[
  {
    "path": ".clang-format",
    "content": "---\nBasedOnStyle: LLVM\nAccessModifierOffset: -4\nAlignAfterOpenBracket: BlockIndent\nAlignEscapedNewlines: Left\nAlignOperands: AlignAfterOperator\nAlignTrailingComments: false\n\n#Allow\nAllowAllArgumentsOnNextLine: false\nAllowAllParametersOfDeclarationOnNextLine: false\nAllowShortBlocksOnASingleLine: Always\nAllowShortFunctionsOnASingleLine: Inline\nAllowShortIfStatementsOnASingleLine: Always\nAllowShortLambdasOnASingleLine: All\nAllowShortEnumsOnASingleLine: false\n\nAlwaysBreakAfterDefinitionReturnType: None\nAlwaysBreakAfterReturnType: None\nAlwaysBreakBeforeMultilineStrings: false\nAlwaysBreakTemplateDeclarations: Yes\n\nBinPackArguments: false\nBinPackParameters: false\n\n#Brace`\nBreakBeforeBraces: Custom\nBraceWrapping:\n  AfterCaseLabel: false\n  AfterClass: false\n  AfterControlStatement: Never\n  AfterEnum: false\n  AfterFunction: false\n  AfterNamespace: false\n  AfterStruct: false\n  AfterUnion: false\n  AfterExternBlock: false\n  BeforeCatch: false\n  BeforeElse: false\n  BeforeWhile: false\n  IndentBraces: false\n  SplitEmptyFunction: false\n  SplitEmptyRecord: false\n  SplitEmptyNamespace: false\n\nBreakBeforeBinaryOperators: NonAssignment\nBreakBeforeTernaryOperators: true\nBreakConstructorInitializers: AfterColon\nBreakInheritanceList: AfterComma\nBreakStringLiterals: true\nColumnLimit: 120\nCompactNamespaces: false\nConstructorInitializerIndentWidth: 2\nContinuationIndentWidth: 2\nCpp11BracedListStyle: true\nDerivePointerAlignment: false\nDisableFormat: false\nEmptyLineAfterAccessModifier: Never\nEmptyLineBeforeAccessModifier: Always\nExperimentalAutoDetectBinPacking: false\nFixNamespaceComments: true\nIncludeBlocks: Regroup\nIncludeCategories:\n  - Regex: '\".*\"'\n    Priority: 1\n  - Regex: '^<.*\\.h>'\n    Priority: 2\n  - Regex: '^<.*'\n    Priority: 3\nIndentAccessModifiers: false\nIndentCaseLabels: true\nIndentPPDirectives: None\nIndentWidth: 4\nIndentWrappedFunctionNames: false\nKeepEmptyLinesAtTheStartOfBlocks: false\nMaxEmptyLinesToKeep: 1\nNamespaceIndentation: All\nPackConstructorInitializers: CurrentLine\n\n#Penalty\nPenaltyBreakAssignment: 40\nPenaltyBreakBeforeFirstCallParameter: 0\nPenaltyBreakComment: 100\nPenaltyBreakFirstLessLess: 0\nPenaltyBreakOpenParenthesis: 0\nPenaltyBreakString: 100\nPenaltyBreakTemplateDeclaration: 0\nPenaltyExcessCharacter: 1\nPenaltyIndentedWhitespace: 0\nPenaltyReturnTypeOnItsOwnLine: 10000\n\nPointerAlignment: Left\nReferenceAlignment: Left\nReflowComments: true\nSeparateDefinitionBlocks: Always\nShortNamespaceLines: 0\nSortIncludes: CaseInsensitive\nSortUsingDeclarations: false\n\n#Space\nSpaceAfterCStyleCast: true\nSpaceAfterLogicalNot: false\nSpaceAfterTemplateKeyword: false\nSpaceBeforeAssignmentOperators: true\nSpaceBeforeCpp11BracedList: true\nSpaceBeforeCtorInitializerColon: true\nSpaceBeforeInheritanceColon: true\nSpaceBeforeParens: ControlStatements\nSpaceBeforeRangeBasedForLoopColon: true\nSpaceInEmptyParentheses: false\nSpacesBeforeTrailingComments: 0\nSpacesInAngles: false\nSpacesInCStyleCastParentheses: false\nSpacesInContainerLiterals: false\nSpacesInLineCommentPrefix:\n  Minimum: 1\n  Maximum: 1\nSpacesInParentheses: false\nSpacesInSquareBrackets: false\n\n#Tab\nTabWidth: 4\nUseTab: Never\n---\nLanguage: Cpp\nStandard: c++17\n...\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Normalize EOL for all files that Git considers text files.\n* text=auto eol=lf\n\n# Ignore some files when exporting to a ZIP.\n# Only include the addons folder when downloading from the Asset Library.\n/**                  export-ignore\n/addons/fmod         !export-ignore\n/addons/fmod/**      !export-ignore"
  },
  {
    "path": ".github/actions/create-android-plugin/action.yaml",
    "content": "name: Create Fmod native build\ndescription: Creates fmod native build for a specific platform\nruns:\n  using: composite\n  steps:\n    - name: Set up JDK 17\n      uses: actions/setup-java@v1\n      with:\n        java-version: 17\n\n    - name: Download fmod jar\n      uses: actions/download-artifact@v4\n      with:\n        name: fmod-jar\n        path: android-plugin/library/libraries/\n\n    - name: Build android plugin\n      uses: eskatos/gradle-command-action@v1\n      with:\n        wrapper-directory: android-plugin/\n        build-root-directory: android-plugin/\n        arguments: :library:build"
  },
  {
    "path": ".github/actions/create-native-build/action.yaml",
    "content": "name: Create Fmod native build\ndescription: Creates fmod native build for a specific platform\ninputs:\n  platform:\n    description: The platform to build for.\n  target:\n    description: The target to build.\n  additional-python-packages:\n    description: Additional python package to install.\n  flags:\n    description: Additional compilation scons flags.\n  fmod-executable-suffix:\n    description: The suffix of fmod executable to install fmod libraries.\n  fmod-user:\n    description: The FMOD user used to download fmod binaries\n  fmod-password:\n    description: The password for fmod account used to download fmod binaries.\n  shell:\n    description: The shell used by the runner.\nruns:\n  using: composite\n  steps:\n    # Use python 3.x release (works cross platform; best to keep self contained in it's own step)\n    - name: Set up Python 3.x\n      uses: actions/setup-python@v4\n      with:\n        # Semantic version range syntax or exact version of a Python version\n        python-version: \"3.x\"\n        # Optional - x64 or x86 architecture, defaults to x64\n        architecture: \"x64\"\n\n    # Setup scons, print python version and scons version info, so if anything is broken it won't run the build.\n    #TODO: remove hardcoded scons version when https://github.com/godotengine/godot-cpp/pull/1526 is released\n    - name: Configuring Python packages\n      shell: ${{ inputs.shell }}\n      run: |\n        python -c \"import sys; print(sys.version)\"\n        python -m pip install scons==4.7.0 requests ${{ inputs.additional-python-packages }}\n        python --version\n        scons --version\n\n    - name: Installing FMOD on Windows\n      shell: ${{ inputs.shell }}\n      if: runner.os == 'Windows'\n      run: |\n        cd ..\n        New-Item -ItemType directory -Path libs; cd libs\n        New-Item -ItemType directory -Path fmod; cd fmod\n        python ../../fmod-gdextension/get_fmod.py ${{inputs.fmod-user}} ${{inputs.fmod-password}} ${{inputs.platform}} ${{env.FMOD_VERSION}}\n        7z x fmodstudioapi${{env.FMOD_VERSION}}${{inputs.fmod-executable-suffix}}\n        ls\n        mv api/ windows\n        cd ../../\n\n    - name: Installing FMOD on Linux & Android\n      if: runner.os == 'Linux'\n      shell: ${{ inputs.shell }}\n      run: |\n        cd ..\n        mkdir libs && cd libs\n        mkdir fmod && cd fmod\n        python ../../fmod-gdextension/get_fmod.py ${{inputs.fmod-user}} ${{inputs.fmod-password}} ${{inputs.platform}} ${{env.FMOD_VERSION}}\n        tar -xvf fmodstudioapi${{env.FMOD_VERSION}}${{inputs.fmod-executable-suffix}}\n        mv fmodstudioapi${{env.FMOD_VERSION}}${{inputs.platform}}/api ${{inputs.platform}}\n        cd ../../\n\n    - name: Installing FMOD on MacOS & iOS\n      if: runner.os == 'MacOS'\n      shell: ${{ inputs.shell }}\n      run: |\n        cd ..\n        mkdir libs && cd libs\n        mkdir fmod && cd fmod\n        python ../../fmod-gdextension/get_fmod.py ${{inputs.fmod-user}} ${{inputs.fmod-password}} ${{inputs.platform}} ${{env.FMOD_VERSION}}\n        hdiutil attach fmodstudioapi${{env.FMOD_VERSION}}${{inputs.fmod-executable-suffix}}\n        [[ ${{inputs.platform}} = \"macos\" ]] && cp -r \"/Volumes/FMOD Programmers API Mac/FMOD Programmers API/api\" osx\n        [[ ${{inputs.platform}} = \"ios\" ]] && cp -r \"/Volumes/FMOD Programmers API iOS/FMOD Programmers API/api\" ios\n        cd ../../\n\n    - name: create android fmod artifact\n      if: inputs.platform == 'android' && inputs.target == 'template_release'\n      shell: ${{ inputs.shell }}\n      run: |\n        mkdir android-fmod-artifact\n        cp ../libs/fmod/android/core/lib/fmod.jar android-fmod-artifact/\n        cp android-plugin/library/src/main/resources/fmod-android-license.txt android-fmod-artifact/\n\n    - name: Upload fmod jar\n      if: inputs.platform == 'android' && inputs.target == 'template_release'\n      uses: actions/upload-artifact@v4\n      with:\n        name: fmod-jar\n        path: android-fmod-artifact\n        if-no-files-found: error\n\n    - name: Get number of CPU cores\n      id: cpu-cores\n      uses: SimenB/github-actions-cpu-cores@v1\n\n    - name: Compilation\n      shell: ${{ inputs.shell }}\n      run: |\n        cd ../fmod-gdextension\n        scons platform=${{ inputs.platform }} generate_bindings=yes target=${{ inputs.target }} target_path=${{ env.TARGET_PATH }} target_name=${{ env.TARGET_NAME }} -j${{ steps.cpu-cores.outputs.count }} ${{ inputs.flags }}\n\n    - name: Upload Artifact\n      uses: actions/upload-artifact@v4\n      with:\n        name: ${{ inputs.platform }}-${{ inputs.target }}\n        path: ${{ env.TARGET_PATH }}${{ inputs.platform }}/**/*.*\n        if-no-files-found: error\n"
  },
  {
    "path": ".github/workflows/check_pr.yml",
    "content": "name: 🌈 Check Pull Request\non:\n  pull_request:\n    types: [opened, synchronize, reopened]\n  pull_request_target:\n    types: [opened, synchronize, reopened]\n\n# Global Settings\nenv:\n  GODOT_VERSION: 4.5\n  NDK_VERSION: 27.3.13750724\n  TARGET_PATH: demo/addons/fmod/libs/\n  TARGET_NAME: libGodotFmod\n  FMOD_VERSION: 20306\njobs:\n  gate:\n    runs-on: ubuntu-latest\n    # EXCLUSIVE PATHS:\n    # - In-repo PRs use pull_request\n    # - External PRs use pull_request_target, so secrets are available, but only after the run is approved\n    if: >\n      (\n        github.event_name == 'pull_request' &&\n        github.event.pull_request.head.repo.full_name == github.repository\n      ) || (\n        github.event_name == 'pull_request_target' &&\n        github.event.pull_request.head.repo.full_name != github.repository\n      )\n    environment: ${{ github.event_name == 'pull_request_target' && 'external-pr' || '' }}\n    steps:\n      - run: echo \"Run Approved\"\n\n  build:\n    name: ${{ matrix.name }}\n    needs: gate\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - name: Windows Editor Compilation\n            os: \"windows-2022\"\n            platform: windows\n            target: editor\n            additional-python-packages: pywin32\n            fmod-executable-suffix: win-installer.exe\n            shell: pwsh\n\n          - name: Windows Debug Compilation\n            os: \"windows-2022\"\n            platform: windows\n            target: template_debug\n            additional-python-packages: pywin32\n            fmod-executable-suffix: win-installer.exe\n            shell: pwsh\n\n          - name: Windows Release Compilation\n            os: \"windows-2022\"\n            platform: windows\n            target: template_release\n            additional-python-packages: pywin32\n            fmod-executable-suffix: win-installer.exe\n            shell: pwsh\n\n          - name: Ubuntu Editor Compilation\n            os: \"ubuntu-22.04\"\n            platform: linux\n            target: editor\n            fmod-executable-suffix: linux.tar.gz\n            fmod-core-platform-folder: linux/core/lib/x86_64\n            fmod-studio-platform-folder: linux/studio/lib/x86_64\n            fmod-library-suffix: so\n            godot-executable-download-suffix: linux.x86_64.zip\n            godot-executable: Godot_v$GODOT_VERSION-stable_linux.x86_64\n            shell: bash\n\n          - name: Ubuntu Debug Compilation\n            os: \"ubuntu-22.04\"\n            platform: linux\n            target: template_debug\n            fmod-executable-suffix: linux.tar.gz\n            shell: bash\n\n          - name: Ubuntu Release Compilation\n            os: \"ubuntu-22.04\"\n            platform: linux\n            target: template_release\n            fmod-executable-suffix: linux.tar.gz\n            shell: bash\n\n          - name: MacOS Editor Compilation\n            os: \"macos-14\"\n            platform: macos\n            target: editor\n            fmod-executable-suffix: osx.dmg\n            fmod-core-platform-folder: osx/core/lib\n            fmod-studio-platform-folder: osx/studio/lib\n            fmod-library-suffix: dylib\n            godot-executable-download-suffix: macos.universal.zip\n            godot-executable: Godot.app/Contents/MacOs/Godot\n            shell: bash\n\n          - name: MacOS Debug Compilation\n            os: \"macos-14\"\n            platform: macos\n            target: template_debug\n            fmod-executable-suffix: osx.dmg\n            shell: bash\n\n          - name: MacOS Release Compilation\n            os: \"macos-14\"\n            platform: macos\n            target: template_release\n            fmod-executable-suffix: osx.dmg\n            shell: bash\n\n          - name: Android Editor Compilation\n            os: \"ubuntu-22.04\"\n            platform: android\n            target: editor\n            fmod-executable-suffix: android.tar.gz\n            flags: ndk_version=$NDK_VERSION arch=arm64\n            shell: bash\n\n          - name: Android Debug Compilation\n            os: \"ubuntu-22.04\"\n            platform: android\n            target: template_debug\n            fmod-executable-suffix: android.tar.gz\n            flags: ndk_version=$NDK_VERSION arch=arm64\n            shell: bash\n\n          - name: Android Release Compilation\n            os: \"ubuntu-22.04\"\n            platform: android\n            target: template_release\n            fmod-executable-suffix: android.tar.gz\n            flags: ndk_version=$NDK_VERSION arch=arm64\n            shell: bash\n\n          - name: iOS Debug Compilation\n            os: \"macos-14\"\n            platform: ios\n            target: template_debug\n            fmod-executable-suffix: ios.dmg\n            shell: bash\n\n          - name: iOS Release Compilation\n            os: \"macos-14\"\n            platform: ios\n            target: template_release\n            fmod-executable-suffix: ios.dmg\n            shell: bash\n\n    steps:\n      - name: Checkout (in-repo PR)\n        if: github.event_name == 'pull_request'\n        uses: actions/checkout@v4\n        with:\n          lfs: true\n          submodules: recursive\n\n      - name: Checkout (external PR via target)\n        if: github.event_name == 'pull_request_target'\n        uses: actions/checkout@v4\n        with:\n          lfs: true\n          submodules: recursive\n          ref: ${{ github.event.pull_request.head.sha }}\n          persist-credentials: false\n\n      - name: Android dependencies\n        if: ${{ matrix.platform == 'android' }}\n        uses: nttld/setup-ndk@v1\n        with:\n          ndk-version: r23c\n          link-to-sdk: true\n\n      - name: Compile native plugin\n        uses: ./.github/actions/create-native-build\n        with:\n          platform: ${{ matrix.platform }}\n          target: ${{ matrix.target }}\n          additional-python-packages: ${{ matrix.additional-python-packages }}\n          flags: ${{ matrix.flags }}\n          fmod-executable-suffix: ${{ matrix.fmod-executable-suffix }}\n          fmod-user: ${{ secrets.FMODUSER }}\n          fmod-password: ${{ secrets.FMODPASS }}\n          shell: ${{ matrix.shell }}\n\n      - name: Download godot engine\n        if: matrix.platform != 'android' && matrix.platform != 'ios' && matrix.platform != 'windows' && matrix.target == 'editor'\n        run: |\n          wget https://github.com/godotengine/godot-builds/releases/download/${{env.GODOT_VERSION}}-stable/Godot_v${{env.GODOT_VERSION}}-stable_${{ matrix.godot-executable-download-suffix }}\n          unzip Godot_v${{env.GODOT_VERSION}}-stable_${{ matrix.godot-executable-download-suffix }}\n          rm Godot_v${{env.GODOT_VERSION}}-stable_${{ matrix.godot-executable-download-suffix }}\n\n      - name: Run tests\n        if: matrix.platform != 'android' && matrix.platform != 'ios' && matrix.platform != 'windows' && matrix.target == 'editor'\n        run: |\n          cd demo\n          chmod +x run_tests.sh\n          chmod +x ../${{ matrix.godot-executable }}\n          ./run_tests.sh ../${{ matrix.godot-executable }}\n\n  create-android-plugin:\n    needs: [build]\n    strategy:\n      matrix:\n        include:\n          - os: \"ubuntu-22.04\"\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n        with:\n          lfs: true\n          submodules: recursive\n\n      - name: Create android plugin\n        uses: ./.github/actions/create-android-plugin\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: 🌈 Release\non:\n  push:\n    tags:\n      - '\\d+.\\d+.\\d+-\\d+.\\d+.\\d+'\n\n# Global Settings\nenv:\n  GODOT_VERSION: 4.5\n  NDK_VERSION: 27.3.13750724\n  TARGET_PATH: demo/addons/fmod/libs/\n  TARGET_NAME: libGodotFmod\n  FMOD_VERSION: 20306\njobs:\n  build:\n    name: ${{ matrix.name }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - name: Windows Editor Compilation\n            os: \"windows-2022\"\n            platform: windows\n            target: editor\n            additional-python-packages: pywin32\n            fmod-executable-suffix: win-installer.exe\n            shell: pwsh\n\n          - name: Windows Debug Compilation\n            os: \"windows-2022\"\n            platform: windows\n            target: template_debug\n            additional-python-packages: pywin32\n            fmod-executable-suffix: win-installer.exe\n            shell: pwsh\n\n          - name: Windows Release Compilation\n            os: \"windows-2022\"\n            platform: windows\n            target: template_release\n            additional-python-packages: pywin32\n            fmod-executable-suffix: win-installer.exe\n            shell: pwsh\n\n          - name: Ubuntu Editor Compilation\n            os: \"ubuntu-22.04\"\n            platform: linux\n            target: editor\n            fmod-executable-suffix: linux.tar.gz\n            fmod-core-platform-folder: linux/core/lib/x86_64\n            fmod-studio-platform-folder: linux/studio/lib/x86_64\n            fmod-library-suffix: so\n            godot-executable-download-suffix: linux.x86_64.zip\n            godot-executable: Godot_v$GODOT_VERSION-stable_linux.x86_64\n            shell: bash\n\n          - name: Ubuntu Debug Compilation\n            os: \"ubuntu-22.04\"\n            platform: linux\n            target: template_debug\n            fmod-executable-suffix: linux.tar.gz\n            shell: bash\n\n          - name: Ubuntu Release Compilation\n            os: \"ubuntu-22.04\"\n            platform: linux\n            target: template_release\n            fmod-executable-suffix: linux.tar.gz\n            shell: bash\n\n          - name: MacOS Editor Compilation\n            os: \"macos-14\"\n            platform: macos\n            target: editor\n            fmod-executable-suffix: osx.dmg\n            fmod-core-platform-folder: osx/core/lib\n            fmod-studio-platform-folder: osx/studio/lib\n            fmod-library-suffix: dylib\n            godot-executable-download-suffix: macos.universal.zip\n            godot-executable: Godot.app/Contents/MacOs/Godot\n            shell: bash\n\n          - name: MacOS Debug Compilation\n            os: \"macos-14\"\n            platform: macos\n            target: template_debug\n            fmod-executable-suffix: osx.dmg\n            shell: bash\n\n          - name: MacOS Release Compilation\n            os: \"macos-14\"\n            platform: macos\n            target: template_release\n            fmod-executable-suffix: osx.dmg\n            shell: bash\n\n          - name: Android Editor Compilation\n            os: \"ubuntu-22.04\"\n            platform: android\n            target: editor\n            fmod-executable-suffix: android.tar.gz\n            flags: ndk_version=$NDK_VERSION arch=arm64\n            shell: bash\n\n          - name: Android Debug Compilation\n            os: \"ubuntu-22.04\"\n            platform: android\n            target: template_debug\n            fmod-executable-suffix: android.tar.gz\n            flags: ndk_version=$NDK_VERSION arch=arm64\n            shell: bash\n\n          - name: Android Release Compilation\n            os: \"ubuntu-22.04\"\n            platform: android\n            target: template_release\n            fmod-executable-suffix: android.tar.gz\n            flags: ndk_version=$NDK_VERSION arch=arm64\n            shell: bash\n\n          - name: iOS Debug Compilation\n            os: \"macos-14\"\n            platform: ios\n            target: template_debug\n            fmod-executable-suffix: ios.dmg\n            shell: bash\n\n          - name: iOS Release Compilation\n            os: \"macos-14\"\n            platform: ios\n            target: template_release\n            fmod-executable-suffix: ios.dmg\n            shell: bash\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          lfs: true\n          submodules: recursive\n\n      - name: Android dependencies\n        if: ${{ matrix.platform == 'android' }}\n        uses: nttld/setup-ndk@v1\n        with:\n          ndk-version: r23c\n          link-to-sdk: true\n\n      - name: Compile native plugin\n        uses: ./.github/actions/create-native-build\n        with:\n          platform: ${{ matrix.platform }}\n          target: ${{ matrix.target }}\n          additional-python-packages: ${{ matrix.additional-python-packages }}\n          flags: ${{ matrix.flags }}\n          fmod-executable-suffix: ${{ matrix.fmod-executable-suffix }}\n          fmod-user: ${{ secrets.FMODUSER }}\n          fmod-password: ${{ secrets.FMODPASS }}\n          shell: ${{ matrix.shell }}\n\n  package-godot-addon:\n    needs: [build]\n    strategy:\n      matrix:\n        include:\n          - os: \"ubuntu-22.04\"\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n        with:\n          lfs: true\n          submodules: recursive\n\n      - name: Create android plugin\n        uses: ./.github/actions/create-android-plugin\n        with:\n          godot-version: ${{ env.GODOT_VERSION }}\n\n      - name: Download linux editor libraries\n        uses: actions/download-artifact@v4\n        with:\n          name: linux-editor\n          path: demo/addons/fmod/libs/linux/\n\n      - name: Download linux template_debug libraries\n        uses: actions/download-artifact@v4\n        with:\n          name: linux-template_debug\n          path: demo/addons/fmod/libs/linux/\n\n      - name: Download linux template_release libraries\n        uses: actions/download-artifact@v4\n        with:\n          name: linux-template_release\n          path: demo/addons/fmod/libs/linux/\n\n      - name: Download windows editor libraries\n        uses: actions/download-artifact@v4\n        with:\n          name: windows-editor\n          path: demo/addons/fmod/libs/windows/\n\n      - name: Download windows template_debug libraries\n        uses: actions/download-artifact@v4\n        with:\n          name: windows-template_debug\n          path: demo/addons/fmod/libs/windows/\n\n      - name: Download windows template_release libraries\n        uses: actions/download-artifact@v4\n        with:\n          name: windows-template_release\n          path: demo/addons/fmod/libs/windows/\n\n      - name: Download macos editor libraries\n        uses: actions/download-artifact@v4\n        with:\n          name: macos-editor\n          path: demo/addons/fmod/libs/macos/\n\n      - name: Download macos template_debug libraries\n        uses: actions/download-artifact@v4\n        with:\n          name: macos-template_debug\n          path: demo/addons/fmod/libs/macos/\n\n      - name: Download macos template_release libraries\n        uses: actions/download-artifact@v4\n        with:\n          name: macos-template_release\n          path: demo/addons/fmod/libs/macos/\n\n      - name: Download iOS template_debug libraries\n        uses: actions/download-artifact@v4\n        with:\n          name: ios-template_debug\n          path: demo/addons/fmod/libs/iOS/\n\n      - name: Download iOS template_release libraries\n        uses: actions/download-artifact@v4\n        with:\n          name: ios-template_release\n          path: demo/addons/fmod/libs/iOS/\n\n      - name: Download android editor libraries\n        uses: actions/download-artifact@v4\n        with:\n          name: android-editor\n          path: demo/addons/fmod/libs/android/\n\n      - name: Download android template_debug libraries\n        uses: actions/download-artifact@v4\n        with:\n          name: android-template_debug\n          path: demo/addons/fmod/libs/android/\n\n      - name: Download android template_release libraries\n        uses: actions/download-artifact@v4\n        with:\n          name: android-template_release\n          path: demo/addons/fmod/libs/android/\n\n      - name: Create Release\n        id: create_release\n        uses: actions/create-release@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          tag_name: ${{ github.ref }}\n          release_name: Release ${{ github.ref }}\n          draft: false\n          prerelease: false\n\n      - name: Zip fmod addon\n        run: |\n          cd demo/addons/\n          zip -r addons.zip fmod -x '*/.gitignore'\n\n      - name: Zip demo project (without .godot and GUT)\n        run: |\n          cd demo\n          zip -r demo.zip . \\\n            -x '*/.gitignore' \\\n               '*.zip' \\\n               '.godot/*' '.godot/**' \\\n               'addons/gut/*' 'addons/gut/**' 'addons/gut' \\\n               '.gut*' \\\n               'run_tests.sh' \\\n               'test/' 'test/*' 'test/**' \\\n\n      - name: Upload Addon Asset\n        uses: actions/upload-release-asset@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps\n          asset_path: ./demo/addons/addons.zip\n          asset_name: addons.zip\n          asset_content_type: application/zip\n\n      - name: Upload Demo Project Asset\n        uses: actions/upload-release-asset@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ./demo/demo.zip\n          asset_name: demo.zip\n          asset_content_type: application/zip\n"
  },
  {
    "path": ".gitignore",
    "content": "\n# Created by https://www.gitignore.io/api/clion,cmake,scons\n# Edit at https://www.gitignore.io/?templates=clion,cmake,scons\n### OSX ###\n\n.DS_Store\n\n### Android ###\n\nlibs/\nobj/\n\n### CLion ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\nCMakeLists.txt\nbin/\n*.os\n*.o\n*.obj\n\n*.exp\n*.lib\n\n# Clion specific\n\n.idea/*\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn.  Uncomment if using\n# auto-import.\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### CLion Patch ###\n# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721\n\n# *.iml\n# modules.xml\n# .idea/misc.xml\n# *.ipr\n\n# Sonarlint plugin\n.idea/sonarlint\n\n### CMake ###\nCMakeLists.txt.user\nCMakeCache.txt\nCMakeFiles\nCMakeScripts\nTesting\nMakefile\ncmake_install.cmake\ninstall_manifest.txt\ncompile_commands.json\nCTestTestfile.cmake\n_deps\n\n### CMake Patch ###\n# External projects\n*-prefix/\n\n### Visual Studio Generated Files ###\n*.vcxproj\n*.vcxproj.filters\n*.sln\n\n### SCons ###\n# for projects that use SCons for building: http://http://www.scons.org/\n.sconsign.dblite\n\n# When configure fails, SCons outputs these\nconfig.log\n.sconf_temp\n\n# End of https://www.gitignore.io/api/clion,cmake,scons\n\n# uncomment when https://github.com/godotengine/godot/issues/71521 fix is in release.\n#demo/.godot\n**/*.editor\n\ndemo/addons/fmod/libs/android/aar/\n\n*.pdb\n.vs"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"godot-cpp\"]\n\tpath = godot-cpp\n\turl = https://github.com/godotengine/godot-cpp\n"
  },
  {
    "path": ".readthedocs.yml",
    "content": "# .readthedocs.yml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\n# Set the OS, Python version and other tools you might need\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.11\"\n\n# Build documentation with MkDocs\nmkdocs:\n configuration: docs/mkdocs.yml\n\n# Optionally set the version of Python and requirements required to build your docs\npython:\n  install:\n    - requirements: docs/requirements.txt\n"
  },
  {
    "path": "Android.mk",
    "content": "# Android.mk\nLOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := fmod-core-prebuilt\nLOCAL_SRC_FILES := ../libs/fmod/android/core/lib/$(TARGET_ARCH_ABI)/libfmod.so\ninclude $(PREBUILT_SHARED_LIBRARY)\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := fmod-studio-prebuilt\nLOCAL_SRC_FILES := ../libs/fmod/android/studio/lib/$(TARGET_ARCH_ABI)/libfmodstudio.so\ninclude $(PREBUILT_SHARED_LIBRARY)\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := godot-prebuilt\nifeq ($(TARGET_ARCH_ABI),x86)\n    LOCAL_SRC_FILES := ../godot-cpp/bin/libgodot-cpp.android.release.x86.a\nendif\nifeq ($(TARGET_ARCH_ABI),armeabi-v7a)\n    LOCAL_SRC_FILES := ../godot-cpp/bin/libgodot-cpp.android.release.armv7.a\nendif\nifeq ($(TARGET_ARCH_ABI),arm64-v8a)\n    LOCAL_SRC_FILES := ../godot-cpp/bin/libgodot-cpp.android.release.arm64v8.a\nendif\ninclude $(PREBUILT_STATIC_LIBRARY)\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := libGodotFmod.android.release.$(TARGET_ARCH_ABI)\nLOCAL_CPPFLAGS := -std=c++14\nLOCAL_CPP_FEATURES := rtti exceptions\nLOCAL_LDLIBS := -llog\n\nLOCAL_SRC_FILES := \\\nsrc/godot_fmod.cpp \\\nsrc/gdlibrary.cpp \\\nsrc/callback/file_callbacks.cpp \\\nsrc/callback/event_callbacks.cpp \\\n\nLOCAL_SHARED_LIBRARIES := \\\nfmod-core-prebuilt \\\nfmod-studio-prebuilt\n\nLOCAL_C_INCLUDES := \\\n../godot-cpp/godot-headers \\\n../godot-cpp/include/ \\\n../godot-cpp/include/core \\\n../godot-cpp/include/gen \\\n../libs/fmod/android/studio/inc \\\n../libs/fmod/android/core/inc\n\nLOCAL_STATIC_LIBRARIES := godot-prebuilt\n\ninclude $(BUILD_SHARED_LIBRARY)\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 Utopia-Rise and Alex Fonseka\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": "README.md",
    "content": "![logo](docs/src/doc/assets/fmod-gdextension-logo.png)\n\n[![🌈 Build](https://github.com/utopia-rise/fmod-gdextension/actions/workflows/release.yml/badge.svg)](https://github.com/utopia-rise/fmod-gdextension/actions/workflows/release.yml) \n[![](https://img.shields.io/discord/1012326818365325352.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.com/invite/u2NM2vTGMn)\n---\n\n**Godot 4 GDExtension that integrates with the FMOD Studio API.** [FMOD is an audio engine and middleware solution](https://www.fmod.com/) for interactive audio in games. It has been the audio engine behind many\ntitles such as **Transistor**, **Into the Breach** and **Celeste**. This Godot extension is used by games such as [Koira](https://dont-nod.com/en/games/koira/).\n\nIf you need any help, you can join our [Discord Server](https://discord.com/invite/u2NM2vTGMn).\n\n# Installation\n\n1. [Download Latest Release](https://github.com/utopia-rise/fmod-gdextension/releases/latest)\n2. Unpack the `addons/fmod` folder into your `/addons` folder within the Godot project\n3. Enable this addon within the Godot settings: `Project > Project Settings > Plugins`\n\n#### Read the [official docs](https://fmod-gdextension.readthedocs.io/en/latest/) to get started with this addon.\n\n# Features\n\n## 🔉 Seamless integration with FMOD\n\nUse FMOD Studio to create bank files this addon will auto-load all events for you inside Godot Engine. Live updating works out of the box.\n\n![fmod-events](docs/src/doc/assets/screenshot-01.png)\n\n## 🔉 Dedicated Godot nodes\n\nThis GDExtension provides nodes such as `FmodEventEmitter2D`, `FmodEventEmitter3D`, `FmodEventListener2D` and `FmodEventListener3D` that can be used in any Godot scene or GDScript code.\n\n![fmod-nodes](docs/src/doc/assets/screenshot-02.png)\n\n# Contributing\n\nIn order to be able to PR this repo from a fork, you need to add `FMODUSER` and `FMODPASS` secrets to your fork repo.  \nThis enables CI to download FMOD api.\n\nFeel free to raise pull requests. We hope you'll enjoy this addon!\n\n## How this extension works\n\nThis GDExtension exposes most of the Studio API functions to Godot's GDScript and also provides helpers for performing\ncommon functions like attaching Studio events to Godot nodes and playing 3D/positional audio.\n\n> **Note:** This plugin doesn't provide C# bindings to FMOD. There is technically a C# FMOD API but we choose to develop it as a C++ GDExtension. Any language binding with a auto-binding feature for extensions should be able to use this plugin, which is the case for GDScript. C# doesn't offer this feature yet.\n\n## Continuous Delivery\n\nThis project uses [Github Actions](https://github.com/features/actions) to continuously deploy released drivers. If you do not want to use those releases, you\ncan compile from sources by looking to [compile from sources section](./docs/src/doc/advanced/1-compiling.md). This project uses [SEMVER](https://semver.org/).\n\n# Special Thanks\n\nThis project is a forked from [godot-fmod-integration](https://github.com/alexfonseka/godot-fmod-integration)\nfrom [alexfonseka](https://github.com/alexfonseka). We'd like to thank him for the work he did, we simply adapted his\nwork to GDNative.\n\n# Tested Versions\n\n- **Godot Version:** 4.4 stable\n- **FMOD Version:** 2.03\n\n[fmodsingleton]: .README/fmodsingleton.png\n[usecustombuild]: .README/usecustombuild.png\n"
  },
  {
    "path": "SConstruct",
    "content": "#!/usr/bin/env python\nimport os\nimport shutil\nimport subprocess\n\nfrom SCons.Script import SConscript, ARGUMENTS, Action, Copy\n\ntarget_path = ARGUMENTS.pop(\"target_path\", \"demo/addons/fmod/libs/\")\ntarget_name = ARGUMENTS.pop(\"target_name\", \"libGodotFmod\")\nfmod_lib_dir = ARGUMENTS.pop(\"fmod_lib_dir\", \"../libs/fmod/\")\n\nenv = SConscript(\"godot-cpp/SConstruct\")\n\n# Add those directory manually, so we can skip the godot_cpp directory when including headers in C++ files\nsource_path = [\n    os.path.join(\"godot-cpp\", \"include\",\"godot_cpp\"),\n    os.path.join(\"godot-cpp\", \"gen\", \"include\",\"godot_cpp\")\n]\nenv.Append(CPPPATH=[env.Dir(d) for d in source_path])\n\nenv.Replace(fmod_lib_dir = fmod_lib_dir)\n\n# For the reference:\n# - CCFLAGS are compilation flags shared between C and C++\n# - CFLAGS are for C-specific compilation flags\n# - CXXFLAGS are for C++-specific compilation flags\n# - CPPFLAGS are for pre-processor flags\n# - CPPDEFINES are for pre-processor defines\n# - LINKFLAGS are for linking flags\n\n# tweak this if you want to use different folders, or more folders, to store your source code in.\nenv.Append(CPPPATH=[\"src/\"])\nsources = [\n    Glob('src/*.cpp'),\n    Glob('src/callback/*.cpp'),\n    Glob('src/core/*.cpp'),\n    Glob('src/data/*.cpp'),\n    Glob('src/tools/*.cpp'),\n    Glob('src/helpers/*.cpp'),\n    Glob('src/nodes/*.cpp'),\n    Glob('src/resources/*.cpp'),\n    Glob('src/studio/*.cpp'),\n    Glob('src/plugins/*.cpp')\n    ]\n\nlfix = \"\"\ndebug = False\nif env[\"target\"] == \"template_debug\" or env[\"target\"] == \"editor\":\n    lfix = \"L\"\n    debug = True\n\nif env[\"platform\"] == \"macos\":\n    libfmod = 'libfmod%s.dylib' % lfix\n    libfmodstudio = 'libfmodstudio%s.dylib' % lfix\n\n    env.Append(CPPPATH=[env['fmod_lib_dir'] + 'osx/core/inc/', env['fmod_lib_dir'] + 'osx/studio/inc/'])\n    env.Append(LIBPATH=[env['fmod_lib_dir'] + 'osx/core/lib/', env['fmod_lib_dir'] + 'osx/studio/lib/'])\n    env.Append(LIBS=[libfmod, libfmodstudio])\n\n    env.Append(\n        LINKFLAGS=[\n            \"-framework\",\n            \"Cocoa\",\n            \"-Wl,-undefined,dynamic_lookup\",\n            \"-rpath\", \"@loader_path/..\"\n        ]\n    )\n\nelif env[\"platform\"] == \"linux\":\n    libfmod = 'libfmod%s.so'% lfix\n    libfmodstudio = 'libfmodstudio%s.so'% lfix\n\n    env.Append(CPPPATH=[env['fmod_lib_dir'] + 'linux/core/inc/', env['fmod_lib_dir'] + 'linux/studio/inc/'])\n    env.Append(LIBPATH=[env['fmod_lib_dir'] + 'linux/core/lib/' + env[\"arch\"], env['fmod_lib_dir'] + 'linux/studio/lib/' + env[\"arch\"]])\n    env.Append(LIBS=[libfmod, libfmodstudio])\n\n    env.Append(CCFLAGS=[\"-fPIC\", \"-Wwrite-strings\"])\n    env.Append(LINKFLAGS=[\"-Wl,-R,'$$ORIGIN'\"])\n    env.Append(LINKFLAGS=[\"-m64\", \"-fuse-ld=gold\"])\n\nelif env[\"platform\"] == \"windows\":\n    libfmod = 'fmod%s_vc'% lfix\n    libfmodstudio = 'fmodstudio%s_vc'% lfix\n    fmod_info_table = {\n        \"x86_64\" : \"x64\",\n        \"x86_32\" : \"x86\",\n    }\n    arch_suffix_override = fmod_info_table[env[\"arch\"]]\n\n    env.Append(CPPPATH=[env['fmod_lib_dir'] + 'windows/core/inc/', env['fmod_lib_dir'] + 'windows/studio/inc/'])\n    env.Append(LIBPATH=[env['fmod_lib_dir'] + 'windows/core/lib/' + arch_suffix_override, env['fmod_lib_dir'] + 'windows/studio/lib/' + arch_suffix_override])\n    env.Append(LIBS=[libfmod, libfmodstudio])\n\n    env.Append(LINKFLAGS=[\"/WX\"])\n    if debug:\n        env.Append(CCFLAGS=[\"/FS\", \"/Zi\"])\n\nelif env[\"platform\"] == \"ios\":\n    libfmod = 'libfmod%s_iphoneos.a' % lfix\n    libfmodstudio = 'libfmodstudio%s_iphoneos.a' % lfix\n\n    env.Append(CPPPATH=[env['fmod_lib_dir'] + 'ios/core/inc/', env['fmod_lib_dir'] + 'ios/studio/inc/'])\n    env.Append(LIBPATH=[env['fmod_lib_dir'] + 'ios/core/lib/', env['fmod_lib_dir'] + 'ios/studio/lib/'])\n    env.Append(LIBS=[libfmod, libfmodstudio])\n\n    env.Append(LINKFLAGS=[\n        '-Wl,-undefined,dynamic_lookup', \"-miphoneos-version-min=\" + env[\"ios_min_version\"]\n    ])\n\nelif env[\"platform\"] == \"android\":\n    libfmod = 'libfmod%s.so' % lfix\n    libfmodstudio = 'libfmodstudio%s.so' % lfix\n    fmod_info_table = {\n        \"armv7\": \"armeabi-v7a\",\n        \"arm64\": \"arm64-v8a\",\n        \"x86\": \"x86\",\n        \"x86_64\": \"x86_64\"\n    }\n    arch_dir = fmod_info_table[env[\"arch\"]]\n\n    env.Append(CPPPATH=[env['fmod_lib_dir'] + 'android/core/inc/', env['fmod_lib_dir'] + 'android/studio/inc/'])\n    env.Append(LIBPATH=[env['fmod_lib_dir'] + 'android/core/lib/' + arch_dir, env['fmod_lib_dir'] + 'android/studio/lib/' + arch_dir])\n    env.Append(LIBS=[libfmod, libfmodstudio])\n\n#Output is placed in the addons directory of the demo project directly\ntarget = \"{}{}/{}.{}.{}\".format(\n    target_path, env[\"platform\"], target_name, env[\"platform\"], env[\"target\"]\n) if env[\"platform\"] != \"android\" else \"{}{}/{}/{}.{}.{}\".format(\n    target_path, env[\"platform\"], env[\"arch\"], target_name, env[\"platform\"], env[\"target\"]\n)\n\nif env[\"platform\"] == \"macos\":\n    target = \"{}.framework/{}.{}.{}\".format(\n        target,\n        target_name,\n        env[\"platform\"],\n        env[\"target\"]\n    )\nelse:\n    target = \"{}.{}{}\".format(\n        target,\n        env[\"arch\"],\n        env[\"SHLIBSUFFIX\"]\n    )\n\nlibrary = env.SharedLibrary(target=target, source=sources)\n\n\ndef sys_exec(args):\n    proc = subprocess.Popen(args, stdout=subprocess.PIPE, text=True)\n    (out, err) = proc.communicate()\n    return out.rstrip(\"\\r\\n\").lstrip()\n\n\nif env[\"platform\"] == \"ios\":\n    xcframework_path = \"{}{}/{}.{}.{}.xcframework\".format(\n        target_path,\n        env[\"platform\"],\n        target_name,\n        env[\"platform\"],\n        env[\"target\"]\n    )\n\n    def create_xcframework(self, arg, env, executor = None):\n        sys_exec([\"xcodebuild\", \"-create-xcframework\", \"-library\", target, \"-output\", xcframework_path])\n        sys_exec([\"rm\", target])\n        sys_exec([\"/usr/libexec/PlistBuddy\", \"-c\", \"Add :MinimumOSVersion string \" + env[\"ios_min_version\"], \"{}/Info.plist\".format(xcframework_path)])\n\n    create_xcframework_action = Action('', create_xcframework)\n\n    AddPostAction(library, create_xcframework_action)\n\n\ndef copy_fmod_libraries(self, arg, env, executor = None):\n    fmod_core_lib_dir = \"\"\n    fmod_studio_lib_dir = \"\"\n\n    addon_fmod_libs_output = \"{}{}/\".format(\n        target_path, env[\"platform\"]\n    ) if env[\"platform\"] != \"android\" else \"{}{}/{}/\".format(\n        target_path, env[\"platform\"], env[\"arch\"]\n    )\n\n    if env[\"platform\"] == \"macos\":\n        fmod_core_lib_dir = env['fmod_lib_dir'] + 'osx/core/lib/'\n        fmod_studio_lib_dir = env['fmod_lib_dir'] + 'osx/studio/lib/'\n    elif env[\"platform\"] == \"linux\":\n        fmod_core_lib_dir = env['fmod_lib_dir'] + 'linux/core/lib/' + env[\"arch\"]\n        fmod_studio_lib_dir = env['fmod_lib_dir'] + 'linux/studio/lib/' + env[\"arch\"]\n    elif env[\"platform\"] == \"windows\":\n        fmod_core_lib_dir = env['fmod_lib_dir'] + 'windows/core/lib/' + arch_suffix_override + '/'\n        fmod_studio_lib_dir = env['fmod_lib_dir'] + 'windows/studio/lib/' + arch_suffix_override + '/'\n    elif env[\"platform\"] == \"ios\":\n        fmod_core_lib_dir = env['fmod_lib_dir'] + 'ios/core/lib/'\n        fmod_studio_lib_dir = env['fmod_lib_dir'] + 'ios/studio/lib/'\n    elif env[\"platform\"] == \"android\":\n        fmod_core_lib_dir = env['fmod_lib_dir'] + 'android/core/lib/' + arch_dir\n        fmod_studio_lib_dir = env['fmod_lib_dir'] + 'android/studio/lib/' + arch_dir\n\n    source_files = [env.Glob(os.path.join(source_dir, '*.*')) for source_dir in [fmod_core_lib_dir, fmod_studio_lib_dir]]\n    [[shutil.copy(str(file), addon_fmod_libs_output) for file in files] for files in source_files]\n\n\ncopy_fmod_libraries_action = Action('', copy_fmod_libraries)\nAddPostAction(library, copy_fmod_libraries_action)\n\nDefault(library)"
  },
  {
    "path": "android-plugin/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.cxx\nlocal.properties\nlibrary/libraries/\n"
  },
  {
    "path": "android-plugin/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nplugins {\n    id(\"com.android.library\") version \"8.0.0\" apply false\n    id(\"org.jetbrains.kotlin.android\") version \"2.1.20\" apply false\n}"
  },
  {
    "path": "android-plugin/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.0-bin.zip\nnetworkTimeout=10000\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "android-plugin/gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\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# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app's APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n# Enables namespacing of each library's R class so that its R class includes only the\n# resources declared in the library itself and none from the library's dependencies,\n# thereby reducing the size of the R class for that library\nandroid.nonTransitiveRClass=true"
  },
  {
    "path": "android-plugin/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\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/subprojects/plugins/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##*/}\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\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# 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    which java >/dev/null 2>&1 || 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.\"\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=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=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# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\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-plugin/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\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.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\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.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\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-plugin/library/.gitignore",
    "content": "/build"
  },
  {
    "path": "android-plugin/library/build.gradle.kts",
    "content": "\nplugins {\n    id(\"com.android.library\")\n    id(\"org.jetbrains.kotlin.android\")\n}\n\nval pluginName = \"fmod\"\nval pluginPackageName = \"com.utopiarise.godot.fmod.android.plugin\"\n\nandroid {\n    namespace = pluginPackageName\n    compileSdk = 33\n\n    defaultConfig {\n        minSdk = 24\n        targetSdk = 33\n\n        manifestPlaceholders[\"godotPluginName\"] = pluginName\n        manifestPlaceholders[\"godotPluginPackageName\"] = pluginPackageName\n        buildConfigField(\"String\", \"GODOT_PLUGIN_NAME\", \"\\\"${pluginName}\\\"\")\n        setProperty(\"archivesBaseName\", pluginName)\n    }\n\n    buildFeatures {\n        buildConfig = true\n    }\n\n    buildTypes {\n        release {\n            isMinifyEnabled = false\n            buildConfigField(\"boolean\", \"DEBUG\", \"false\")\n        }\n\n        debug {\n            buildConfigField(\"boolean\", \"DEBUG\", \"true\")\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n    kotlinOptions {\n        jvmTarget = \"17\"\n    }\n}\n\nval copyDebugAARToDemoAddons by tasks.registering(Copy::class) {\n    description = \"Copies the generated debug AAR binary to the plugin's addons directory\"\n    from(\"build/outputs/aar\")\n    include(\"$pluginName-debug.aar\")\n    into(\"../../demo/addons/$pluginName/libs/android\")\n}\n\nval copyReleaseAARToDemoAddons by tasks.registering(Copy::class) {\n    description = \"Copies the generated release AAR binary to the plugin's addons directory\"\n    from(\"build/outputs/aar\")\n    include(\"$pluginName-release.aar\")\n    into(\"../../demo/addons/$pluginName/libs/android\")\n}\n\n\ntasks.named(\"assemble\").configure {\n    finalizedBy(copyDebugAARToDemoAddons)\n    finalizedBy(copyReleaseAARToDemoAddons)\n}\n\ndependencies {\n    implementation(files(\"libraries/fmod.jar\"))\n    implementation(\"org.godotengine:godot:4.5.0.stable\")\n}"
  },
  {
    "path": "android-plugin/library/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>\n\n    <application>\n        <meta-data\n                android:name=\"org.godotengine.plugin.v2.FmodPlugin\"\n                android:value=\"com.utopiarise.godot.fmod.android.plugin.FmodPlugin\" />\n    </application>\n\n</manifest>"
  },
  {
    "path": "android-plugin/library/src/main/kotlin/com/utopiarise/godot/fmod/android/plugin/FmodPlugin.kt",
    "content": "package com.utopiarise.godot.fmod.android.plugin\n\nimport android.app.Activity\nimport android.view.View\nimport org.fmod.FMOD\nimport org.godotengine.godot.Godot\nimport org.godotengine.godot.plugin.GodotPlugin\n\nclass FmodPlugin(godot: Godot) : GodotPlugin(godot) {\n    override fun getPluginName() = \"Godot Fmod Android Plugin\"\n\n    override fun onMainCreate(activity: Activity?): View? {\n        FMOD.init(activity)\n        return super.onMainCreate(activity)\n    }\n\n    override fun onMainDestroy() {\n        FMOD.close()\n    }\n\n    companion object {\n        init {\n            if (BuildConfig.DEBUG) {\n                System.loadLibrary(\"fmodL\")\n                System.loadLibrary(\"fmodstudioL\")\n            } else {\n                System.loadLibrary(\"fmod\")\n                System.loadLibrary(\"fmodstudio\")\n            }\n        }\n    }\n}"
  },
  {
    "path": "android-plugin/library/src/main/resources/fmod-android-license.txt",
    "content": "Copyright (C) 2010 The Android Open Source Project All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the\nfollowing conditions are met:\n- Redistributions of source code must retain the above copyright notice, this list of conditions and the following\ndisclaimer.\n- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following\ndisclaimer in the documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\nINCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\nWHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\nTHIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
  },
  {
    "path": "android-plugin/settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n\nrootProject.name = \"Godot Fmod Android Plugin\"\ninclude(\":library\")\n "
  },
  {
    "path": "demo/.gitattributes",
    "content": "# Normalize EOL for all files that Git considers text files.\n* text=auto eol=lf\n"
  },
  {
    "path": "demo/.gitignore",
    "content": "\n# Godot 4+ specific ignores\n.godot/\n\n# Godot-specific ignores\n.import/\nexport.cfg\nexport_credentials.cfg\n\n# Imported translations (automatically generated from CSV files)\n*.translation\n\n# Mono-specific ignores\n.mono/\ndata_*/\nmono_crash.*.json\n\n.vscode/\nlogs/\n**/*.aar\nandroid/build\nandroid/"
  },
  {
    "path": "demo/.gutconfig.json",
    "content": "{\n \"background_color\": \"262626ff\",\n \"compact_mode\": false,\n \"configured_dirs\": [\n  \"res://test/unit\"\n ],\n \"dirs\": [\n  \"res://test/unit\"\n ],\n \"disable_colors\": false,\n \"double_strategy\": 1,\n \"failure_error_types\": [\n  \"gut\"\n ],\n \"font_color\": \"ccccccff\",\n \"font_name\": \"CourierPrime\",\n \"font_size\": 16.0,\n \"gut_on_top\": true,\n \"hide_orphans\": false,\n \"ignore_pause\": false,\n \"include_subdirs\": false,\n \"junit_xml_file\": \"\",\n \"junit_xml_timestamp\": false,\n \"log_level\": 1.0,\n \"no_error_tracking\": false,\n \"opacity\": 100.0,\n \"paint_after\": 0.1,\n \"post_run_script\": \"\",\n \"pre_run_script\": \"\",\n \"prefix\": \"test_\",\n \"should_exit\": true,\n \"should_exit_on_success\": false,\n \"should_maximize\": false,\n \"suffix\": \".gd\",\n \"wait_log_delay\": 0.5\n}\n"
  },
  {
    "path": "demo/addons/fmod/.gitignore",
    "content": "*.dll\n*.a\n*.dylib\n*.so\n*.so.*\n*.xcframework\n!libs/"
  },
  {
    "path": "demo/addons/fmod/FmodAndroidExportPlugin.gd",
    "content": "@tool\nclass_name FmodAndroidExportPlugin extends EditorExportPlugin\n\nvar plugin_name: String = \"fmod\"\n\nfunc _supports_platform(platform):\n\tif platform is EditorExportPlatformAndroid:\n\t\treturn true\n\treturn false\n\nfunc _get_android_libraries(platform, debug):\n\tif debug:\n\t\treturn PackedStringArray([plugin_name + \"/libs/android/\" + plugin_name + \"-debug.aar\"])\n\telse:\n\t\treturn PackedStringArray([plugin_name + \"/libs/android/\" + plugin_name + \"-release.aar\"])\n\n\nfunc _get_name():\n\treturn plugin_name\n"
  },
  {
    "path": "demo/addons/fmod/FmodAndroidExportPlugin.gd.uid",
    "content": "uid://cle6f2srp4yun\n"
  },
  {
    "path": "demo/addons/fmod/FmodManager.gd",
    "content": "@tool\nextends Node\n\nvar performance_display: PerformancesDisplay\n\nfunc _ready():\n\tprocess_mode = PROCESS_MODE_ALWAYS\n\tperformance_display = PerformancesDisplay.new()\n\tadd_child(performance_display)\n\nfunc _exit_tree() -> void:\n\tremove_child(performance_display)\n\tperformance_display.free()\n\nfunc _process(delta):\n\tFmodServer.update()\n\t\nfunc _notification(what):\n\tFmodServer.notification(what)\n\n\tif OS.has_feature(\"mobile\"):\n\t\tmatch what:\n\t\t\tNOTIFICATION_APPLICATION_FOCUS_OUT: FmodServer.mixer_suspend()\n\t\t\tNOTIFICATION_APPLICATION_FOCUS_IN: FmodServer.mixer_resume()\n"
  },
  {
    "path": "demo/addons/fmod/FmodManager.gd.uid",
    "content": "uid://cds10pm7hwn3p\n"
  },
  {
    "path": "demo/addons/fmod/FmodPlugin.gd",
    "content": "@tool\nclass_name FmodPlugin extends EditorPlugin\n\n\nconst ADDON_PATH: StringName = &\"res://addons/fmod\"\nconst FmodManager_Autoload_Name: StringName = &\"FmodManager\"\n@onready var theme: Theme = get_editor_interface().get_base_control().get_theme()\n\nvar fmod_bank_explorer_window: PackedScene = load(\"res://addons/fmod/tool/ui/FmodBankExplorer.tscn\")\nvar bank_explorer: FmodBankExplorer\nvar fmod_button: Button\nvar export_plugin: FmodEditorExportPlugin = FmodEditorExportPlugin.new()\nvar android_export_plugin: FmodAndroidExportPlugin = FmodAndroidExportPlugin.new()\nvar emitter_inspector_plugin: FmodEmitterPropertyInspectorPlugin = FmodEmitterPropertyInspectorPlugin.new(self)\nvar bank_loader_inspector_plugin: FmodBankLoaderPropertyInspectorPlugin = FmodBankLoaderPropertyInspectorPlugin.new(self)\n\nfunc _init() -> void:\n\tFmodBankDatabase.reload_all_banks()\n\nfunc _enable_plugin() -> void:\n\tadd_autoload_singleton(FmodManager_Autoload_Name, \"res://addons/fmod/FmodManager.gd\")\n\nfunc _disable_plugin() -> void:\n\tremove_autoload_singleton(FmodManager_Autoload_Name)\n\nfunc _enter_tree() -> void:\n\t_add_explorer_button()\n\t\n\t_add_bank_explorer_window()\n\n\tadd_inspector_plugin(bank_loader_inspector_plugin)\n\tadd_inspector_plugin(emitter_inspector_plugin)\n\t\n\tadd_export_plugin(export_plugin)\n\tadd_export_plugin(android_export_plugin)\n\nfunc _exit_tree() -> void:\n\tremove_control_from_container(EditorPlugin.CONTAINER_TOOLBAR, fmod_button)\n\tfmod_button.queue_free()\n\t\n\tbank_explorer.queue_free()\n\t\n\tremove_inspector_plugin(emitter_inspector_plugin)\n\tremove_inspector_plugin(bank_loader_inspector_plugin)\n\n\tremove_export_plugin(android_export_plugin)\n\tremove_export_plugin(export_plugin)\n\nfunc _add_explorer_button() -> void:\n\tfmod_button = Button.new()\n\tfmod_button.icon = load(\"res://addons/fmod/icons/fmod_icon.svg\")\n\tfmod_button.text = \"Fmod Explorer\"\n\t\n\tfmod_button.pressed.connect(_on_project_explorer_button_clicked)\n\t\n\tadd_control_to_container(EditorPlugin.CONTAINER_TOOLBAR, fmod_button)\n\nfunc _add_bank_explorer_window() -> void:\n\tbank_explorer = fmod_bank_explorer_window.instantiate()\n\tbank_explorer.theme = get_editor_interface().get_base_control().get_theme()\n\tbank_explorer.visible = false\n\t\n\tadd_child(bank_explorer)\n\nfunc _on_project_explorer_button_clicked() -> void:\n\tbank_explorer.should_display_copy_buttons = true\n\tbank_explorer.should_display_select_button = false\n\t_popup_project_explorer(FmodBankExplorer.ToDisplayFlags.BUSES | FmodBankExplorer.ToDisplayFlags.VCA | FmodBankExplorer.ToDisplayFlags.EVENTS)\n\nfunc open_project_explorer_events(on_select_callable: Callable) -> void:\n\t_open_project_explorer(FmodBankExplorer.ToDisplayFlags.EVENTS, on_select_callable)\n\nfunc open_project_explorer_bank(on_select_callable: Callable) -> void:\n\t_open_project_explorer(0, on_select_callable)\n\nfunc _open_project_explorer(display_flag: int, on_select_callable: Callable) -> void:\n\tbank_explorer.should_display_copy_buttons = false\n\tbank_explorer.should_display_select_button = true\n\t_popup_project_explorer(display_flag, on_select_callable)\n\nfunc _popup_project_explorer(to_display: int, callable: Callable = Callable()) -> void:\n\tif bank_explorer.visible == true:\n\t\tbank_explorer.close_window()\n\t\treturn\n\t\n\tbank_explorer.flags = to_display\n\tbank_explorer.reset_search()\n\tbank_explorer.regenerate_tree(callable)\n\tbank_explorer.popup_centered()\n\t\n"
  },
  {
    "path": "demo/addons/fmod/FmodPlugin.gd.uid",
    "content": "uid://cwsif6rhp50p5\n"
  },
  {
    "path": "demo/addons/fmod/fmod.gdextension",
    "content": "[configuration]\nentry_symbol = \"fmod_library_init\"\ncompatibility_minimum = 4.5\n\n[libraries]\nwindows.editor.x86_64 = \"res://addons/fmod/libs/windows/libGodotFmod.windows.editor.x86_64.dll\"\nwindows.debug.x86_64 = \"res://addons/fmod/libs/windows/libGodotFmod.windows.template_debug.x86_64.dll\"\nwindows.release.x86_64 = \"res://addons/fmod/libs/windows/libGodotFmod.windows.template_release.x86_64.dll\"\nmacos.editor = \"res://addons/fmod/libs/macos/libGodotFmod.macos.editor.framework\"\nmacos.debug  = \"res://addons/fmod/libs/macos/libGodotFmod.macos.template_debug.framework\"\nmacos.release  = \"res://addons/fmod/libs/macos/libGodotFmod.macos.template_release.framework\"\nlinux.editor.x86_64 = \"res://addons/fmod/libs/linux/libGodotFmod.linux.editor.x86_64.so\"\nlinux.debug.x86_64 = \"res://addons/fmod/libs/linux/libGodotFmod.linux.template_debug.x86_64.so\"\nlinux.release.x86_64 = \"res://addons/fmod/libs/linux/libGodotFmod.linux.template_release.x86_64.so\"\nandroid.debug.x86_64 = \"res://addons/fmod/libs/android/x86_64/libGodotFmod.android.template_debug.x86_64.so\"\nandroid.release.x86_64 = \"res://addons/fmod/libs/android/x86_64/libGodotFmod.android.template_release.x86_64.so\"\nandroid.debug.arm64 = \"res://addons/fmod/libs/android/arm64/libGodotFmod.android.template_debug.arm64.so\"\nandroid.release.arm64 = \"res://addons/fmod/libs/android/arm64/libGodotFmod.android.template_release.arm64.so\"\nios.debug  = \"res://addons/fmod/libs/ios/libGodotFmod.ios.template_debug.xcframework\"\nios.release  = \"res://addons/fmod/libs/ios/libGodotFmod.ios.template_release.xcframework\"\n\n[icons]\nFmodEventEmitter2D = \"res://addons/fmod/icons/fmod_icon.svg\"\nFmodEventEmitter3D = \"res://addons/fmod/icons/fmod_icon.svg\"\nFmodListener2D = \"res://addons/fmod/icons/fmod_icon.svg\"\nFmodListener3D = \"res://addons/fmod/icons/fmod_icon.svg\"\nFmodBankLoader = \"res://addons/fmod/icons/fmod_icon.svg\"\n\n[dependencies]\nwindows.editor.x86_64 = {\n\"libs/windows/fmodL.dll\": \"\",\n\"libs/windows/fmodstudioL.dll\": \"\"\n}\nwindows.debug.x86_64 = {\n\"libs/windows/fmodL.dll\": \"\", \n\"libs/windows/fmodstudioL.dll\": \"\"\n}\nwindows.release.x86_64 = {\n\"libs/windows/fmod.dll\": \"\", \n\"libs/windows/fmodstudio.dll\": \"\"\n}\nlinux.editor.x86_64  = {\n\"libs/linux/libfmodL.so\": \"\",\n\"libs/linux/libfmodL.so.14\": \"\",\n\"libs/linux/libfmodL.so.14.6\": \"\",\n\"libs/linux/libfmodstudioL.so\": \"\",\n\"libs/linux/libfmodstudioL.so.14\": \"\",\n\"libs/linux/libfmodstudioL.so.14.6\": \"\"\n}\nlinux.debug.x86_64  = {\n\"libs/linux/libfmodL.so\": \"\",\n\"libs/linux/libfmodL.so.14\": \"\",\n\"libs/linux/libfmodL.so.14.6\": \"\",\n\"libs/linux/libfmodstudioL.so\": \"\",\n\"libs/linux/libfmodstudioL.so.14\": \"\",\n\"libs/linux/libfmodstudioL.so.14.6\": \"\"\n}\nlinux.release.x86_64 = {\n\"libs/linux/libfmod.so\": \"\",\n\"libs/linux/libfmod.so.14\": \"\",\n\"libs/linux/libfmod.so.14.6\": \"\",\n\"libs/linux/libfmodstudio.so\": \"\",\n\"libs/linux/libfmodstudio.so.14\": \"\",\n\"libs/linux/libfmodstudio.so.14.6\": \"\"\n}\nmacos.editor  = {\n\"libs/macos/libfmodL.dylib\": \"\",\n\"libs/macos/libfmodstudioL.dylib\": \"\"\n}\nmacos.debug  = {\n\"libs/macos/libfmodL.dylib\": \"\",\n\"libs/macos/libfmodstudioL.dylib\": \"\"\n}\nmacos.release = {\n\"libs/macos/libfmod.dylib\": \"\",\n\"libs/macos/libfmodstudio.dylib\": \"\"\n}\nandroid.debug.x86_64 = {\n\"libs/android/x86_64/libfmodL.so\": \"\",\n\"libs/android/x86_64/libfmodstudioL.so\": \"\",\n}\nandroid.release.x86_64 = {\n\"libs/android/x86_64/libfmod.so\": \"\",\n\"libs/android/x86_64/libfmodstudio.so\": \"\",\n}\nandroid.debug.arm64 = {\n\"libs/android/arm64/libfmodL.so\": \"\",\n\"libs/android/arm64/libfmodstudioL.so\": \"\",\n}\nandroid.release.arm64 = {\n\"libs/android/arm64/libfmod.so\": \"\",\n\"libs/android/arm64/libfmodstudio.so\": \"\",\n}\nios.debug  = {\n\"libs/ios/libfmodL_iphoneos.a\": \"\",\n\"libs/ios/libfmodstudioL_iphoneos.a\": \"\"\n}\nios.release = {\n\"libs/ios/libfmod_iphoneos.a\": \"\",\n\"libs/ios/libfmodstudio_iphoneos.a\": \"\"\n}"
  },
  {
    "path": "demo/addons/fmod/fmod.gdextension.uid",
    "content": "uid://dq17s7r52klxe\n"
  },
  {
    "path": "demo/addons/fmod/icons/bank_icon.svg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://o2chsr07oeqs\"\npath=\"res://.godot/imported/bank_icon.svg-8de6c7bff09a67352e162b3c61b601de.ctex\"\nmetadata={\n\"has_editor_variant\": true,\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://addons/fmod/icons/bank_icon.svg\"\ndest_files=[\"res://.godot/imported/bank_icon.svg-8de6c7bff09a67352e162b3c61b601de.ctex\"]\n\n[params]\n\ncompress/mode=0\ncompress/high_quality=false\ncompress/lossy_quality=0.7\ncompress/uastc_level=0\ncompress/rdo_quality_loss=0.0\ncompress/hdr_compression=1\ncompress/normal_map=0\ncompress/channel_pack=0\nmipmaps/generate=false\nmipmaps/limit=-1\nroughness/mode=0\nroughness/src_normal=\"\"\nprocess/channel_remap/red=0\nprocess/channel_remap/green=1\nprocess/channel_remap/blue=2\nprocess/channel_remap/alpha=3\nprocess/fix_alpha_border=true\nprocess/premult_alpha=false\nprocess/normal_map_invert_y=false\nprocess/hdr_as_srgb=false\nprocess/hdr_clamp_exposure=false\nprocess/size_limit=0\ndetect_3d/compress_to=1\nsvg/scale=1.0\neditor/scale_with_editor_scale=true\neditor/convert_colors_with_editor_theme=true\n"
  },
  {
    "path": "demo/addons/fmod/icons/bus_icon.svg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://dj1kag4aeg58t\"\npath=\"res://.godot/imported/bus_icon.svg-f536ffd3c4893e79a9d3cb9a1b4fb50c.ctex\"\nmetadata={\n\"has_editor_variant\": true,\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://addons/fmod/icons/bus_icon.svg\"\ndest_files=[\"res://.godot/imported/bus_icon.svg-f536ffd3c4893e79a9d3cb9a1b4fb50c.ctex\"]\n\n[params]\n\ncompress/mode=0\ncompress/high_quality=false\ncompress/lossy_quality=0.7\ncompress/uastc_level=0\ncompress/rdo_quality_loss=0.0\ncompress/hdr_compression=1\ncompress/normal_map=0\ncompress/channel_pack=0\nmipmaps/generate=false\nmipmaps/limit=-1\nroughness/mode=0\nroughness/src_normal=\"\"\nprocess/channel_remap/red=0\nprocess/channel_remap/green=1\nprocess/channel_remap/blue=2\nprocess/channel_remap/alpha=3\nprocess/fix_alpha_border=true\nprocess/premult_alpha=false\nprocess/normal_map_invert_y=false\nprocess/hdr_as_srgb=false\nprocess/hdr_clamp_exposure=false\nprocess/size_limit=0\ndetect_3d/compress_to=1\nsvg/scale=1.0\neditor/scale_with_editor_scale=true\neditor/convert_colors_with_editor_theme=true\n"
  },
  {
    "path": "demo/addons/fmod/icons/c_parameter_icon.svg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://cmvcqfsl167te\"\npath=\"res://.godot/imported/c_parameter_icon.svg-04115c2482c9a6874356ffdc09c41db0.ctex\"\nmetadata={\n\"has_editor_variant\": true,\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://addons/fmod/icons/c_parameter_icon.svg\"\ndest_files=[\"res://.godot/imported/c_parameter_icon.svg-04115c2482c9a6874356ffdc09c41db0.ctex\"]\n\n[params]\n\ncompress/mode=0\ncompress/high_quality=false\ncompress/lossy_quality=0.7\ncompress/uastc_level=0\ncompress/rdo_quality_loss=0.0\ncompress/hdr_compression=1\ncompress/normal_map=0\ncompress/channel_pack=0\nmipmaps/generate=false\nmipmaps/limit=-1\nroughness/mode=0\nroughness/src_normal=\"\"\nprocess/channel_remap/red=0\nprocess/channel_remap/green=1\nprocess/channel_remap/blue=2\nprocess/channel_remap/alpha=3\nprocess/fix_alpha_border=true\nprocess/premult_alpha=false\nprocess/normal_map_invert_y=false\nprocess/hdr_as_srgb=false\nprocess/hdr_clamp_exposure=false\nprocess/size_limit=0\ndetect_3d/compress_to=1\nsvg/scale=1.0\neditor/scale_with_editor_scale=true\neditor/convert_colors_with_editor_theme=true\n"
  },
  {
    "path": "demo/addons/fmod/icons/d_parameter_icon.svg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://dgna04txtwnyb\"\npath=\"res://.godot/imported/d_parameter_icon.svg-d339e4e3f950ae8593b999ef51579136.ctex\"\nmetadata={\n\"has_editor_variant\": true,\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://addons/fmod/icons/d_parameter_icon.svg\"\ndest_files=[\"res://.godot/imported/d_parameter_icon.svg-d339e4e3f950ae8593b999ef51579136.ctex\"]\n\n[params]\n\ncompress/mode=0\ncompress/high_quality=false\ncompress/lossy_quality=0.7\ncompress/uastc_level=0\ncompress/rdo_quality_loss=0.0\ncompress/hdr_compression=1\ncompress/normal_map=0\ncompress/channel_pack=0\nmipmaps/generate=false\nmipmaps/limit=-1\nroughness/mode=0\nroughness/src_normal=\"\"\nprocess/channel_remap/red=0\nprocess/channel_remap/green=1\nprocess/channel_remap/blue=2\nprocess/channel_remap/alpha=3\nprocess/fix_alpha_border=true\nprocess/premult_alpha=false\nprocess/normal_map_invert_y=false\nprocess/hdr_as_srgb=false\nprocess/hdr_clamp_exposure=false\nprocess/size_limit=0\ndetect_3d/compress_to=1\nsvg/scale=1.0\neditor/scale_with_editor_scale=true\neditor/convert_colors_with_editor_theme=true\n"
  },
  {
    "path": "demo/addons/fmod/icons/event_icon.svg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://cmpgmbn3y4svl\"\npath=\"res://.godot/imported/event_icon.svg-4e6e2103d076f95b7bef82f079e433e6.ctex\"\nmetadata={\n\"has_editor_variant\": true,\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://addons/fmod/icons/event_icon.svg\"\ndest_files=[\"res://.godot/imported/event_icon.svg-4e6e2103d076f95b7bef82f079e433e6.ctex\"]\n\n[params]\n\ncompress/mode=0\ncompress/high_quality=false\ncompress/lossy_quality=0.7\ncompress/uastc_level=0\ncompress/rdo_quality_loss=0.0\ncompress/hdr_compression=1\ncompress/normal_map=0\ncompress/channel_pack=0\nmipmaps/generate=false\nmipmaps/limit=-1\nroughness/mode=0\nroughness/src_normal=\"\"\nprocess/channel_remap/red=0\nprocess/channel_remap/green=1\nprocess/channel_remap/blue=2\nprocess/channel_remap/alpha=3\nprocess/fix_alpha_border=true\nprocess/premult_alpha=false\nprocess/normal_map_invert_y=false\nprocess/hdr_as_srgb=false\nprocess/hdr_clamp_exposure=false\nprocess/size_limit=0\ndetect_3d/compress_to=1\nsvg/scale=1.0\neditor/scale_with_editor_scale=true\neditor/convert_colors_with_editor_theme=true\n"
  },
  {
    "path": "demo/addons/fmod/icons/fmod_emitter.png.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://cotpb54utx6d6\"\npath=\"res://.godot/imported/fmod_emitter.png-6783a287e298e2a04e64a6deaa6fe366.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://addons/fmod/icons/fmod_emitter.png\"\ndest_files=[\"res://.godot/imported/fmod_emitter.png-6783a287e298e2a04e64a6deaa6fe366.ctex\"]\n\n[params]\n\ncompress/mode=0\ncompress/high_quality=false\ncompress/lossy_quality=0.7\ncompress/uastc_level=0\ncompress/rdo_quality_loss=0.0\ncompress/hdr_compression=1\ncompress/normal_map=0\ncompress/channel_pack=0\nmipmaps/generate=false\nmipmaps/limit=-1\nroughness/mode=0\nroughness/src_normal=\"\"\nprocess/channel_remap/red=0\nprocess/channel_remap/green=1\nprocess/channel_remap/blue=2\nprocess/channel_remap/alpha=3\nprocess/fix_alpha_border=true\nprocess/premult_alpha=false\nprocess/normal_map_invert_y=false\nprocess/hdr_as_srgb=false\nprocess/hdr_clamp_exposure=false\nprocess/size_limit=0\ndetect_3d/compress_to=1\n"
  },
  {
    "path": "demo/addons/fmod/icons/fmod_icon.svg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://bwqq5q7kodk40\"\npath=\"res://.godot/imported/fmod_icon.svg-cca7eb13231881fafaea0d598d407ea3.ctex\"\nmetadata={\n\"has_editor_variant\": true,\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://addons/fmod/icons/fmod_icon.svg\"\ndest_files=[\"res://.godot/imported/fmod_icon.svg-cca7eb13231881fafaea0d598d407ea3.ctex\"]\n\n[params]\n\ncompress/mode=0\ncompress/high_quality=false\ncompress/lossy_quality=0.7\ncompress/uastc_level=0\ncompress/rdo_quality_loss=0.0\ncompress/hdr_compression=1\ncompress/normal_map=0\ncompress/channel_pack=0\nmipmaps/generate=false\nmipmaps/limit=-1\nroughness/mode=0\nroughness/src_normal=\"\"\nprocess/channel_remap/red=0\nprocess/channel_remap/green=1\nprocess/channel_remap/blue=2\nprocess/channel_remap/alpha=3\nprocess/fix_alpha_border=true\nprocess/premult_alpha=false\nprocess/normal_map_invert_y=false\nprocess/hdr_as_srgb=false\nprocess/hdr_clamp_exposure=false\nprocess/size_limit=0\ndetect_3d/compress_to=1\nsvg/scale=1.0\neditor/scale_with_editor_scale=true\neditor/convert_colors_with_editor_theme=true\n"
  },
  {
    "path": "demo/addons/fmod/icons/snapshot_icon.svg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://b4jxbh86chubi\"\npath=\"res://.godot/imported/snapshot_icon.svg-7b517248819b3685844766808fbce2a5.ctex\"\nmetadata={\n\"has_editor_variant\": true,\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://addons/fmod/icons/snapshot_icon.svg\"\ndest_files=[\"res://.godot/imported/snapshot_icon.svg-7b517248819b3685844766808fbce2a5.ctex\"]\n\n[params]\n\ncompress/mode=0\ncompress/high_quality=false\ncompress/lossy_quality=0.7\ncompress/uastc_level=0\ncompress/rdo_quality_loss=0.0\ncompress/hdr_compression=1\ncompress/normal_map=0\ncompress/channel_pack=0\nmipmaps/generate=false\nmipmaps/limit=-1\nroughness/mode=0\nroughness/src_normal=\"\"\nprocess/channel_remap/red=0\nprocess/channel_remap/green=1\nprocess/channel_remap/blue=2\nprocess/channel_remap/alpha=3\nprocess/fix_alpha_border=true\nprocess/premult_alpha=false\nprocess/normal_map_invert_y=false\nprocess/hdr_as_srgb=false\nprocess/hdr_clamp_exposure=false\nprocess/size_limit=0\ndetect_3d/compress_to=1\nsvg/scale=1.0\neditor/scale_with_editor_scale=true\neditor/convert_colors_with_editor_theme=true\n"
  },
  {
    "path": "demo/addons/fmod/icons/vca_icon.svg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://crsj4jjaeq87a\"\npath=\"res://.godot/imported/vca_icon.svg-def43f27fe148a7a0b18c7dcaab20c79.ctex\"\nmetadata={\n\"has_editor_variant\": true,\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://addons/fmod/icons/vca_icon.svg\"\ndest_files=[\"res://.godot/imported/vca_icon.svg-def43f27fe148a7a0b18c7dcaab20c79.ctex\"]\n\n[params]\n\ncompress/mode=0\ncompress/high_quality=false\ncompress/lossy_quality=0.7\ncompress/uastc_level=0\ncompress/rdo_quality_loss=0.0\ncompress/hdr_compression=1\ncompress/normal_map=0\ncompress/channel_pack=0\nmipmaps/generate=false\nmipmaps/limit=-1\nroughness/mode=0\nroughness/src_normal=\"\"\nprocess/channel_remap/red=0\nprocess/channel_remap/green=1\nprocess/channel_remap/blue=2\nprocess/channel_remap/alpha=3\nprocess/fix_alpha_border=true\nprocess/premult_alpha=false\nprocess/normal_map_invert_y=false\nprocess/hdr_as_srgb=false\nprocess/hdr_clamp_exposure=false\nprocess/size_limit=0\ndetect_3d/compress_to=1\nsvg/scale=1.0\neditor/scale_with_editor_scale=true\neditor/convert_colors_with_editor_theme=true\n"
  },
  {
    "path": "demo/addons/fmod/libs/android/arm64/CopyPast_Fmod_Libs_Here.txt",
    "content": ""
  },
  {
    "path": "demo/addons/fmod/libs/iOS/CopyPast_Fmod_Libs_Here.txt",
    "content": ""
  },
  {
    "path": "demo/addons/fmod/libs/linux/CopyPast_Fmod_Libs_Here.txt",
    "content": ""
  },
  {
    "path": "demo/addons/fmod/libs/macos/CopyPast_Fmod_Libs_Here.txt",
    "content": ""
  },
  {
    "path": "demo/addons/fmod/libs/macos/libGodotFmod.macos.editor.framework/Info.plist",
    "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>CFBundleExecutable</key>\n\t<string>libGodotFmod.macos.editor</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>com.utopia-rise.fmod-gdextension</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>libGodotFmod.macos.editor</string>\n\t<key>CFBundlePackageType</key>\n\t<string>FMWK</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0.0</string>\n\t<key>CFBundleSupportedPlatforms</key>\n\t<array>\n\t\t<string>MacOSX</string>\n\t</array>\n\t<key>CFBundleVersion</key>\n\t<string>1.0.0</string>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>10.12</string>\n</dict>\n</plist>"
  },
  {
    "path": "demo/addons/fmod/libs/macos/libGodotFmod.macos.template_debug.framework/Info.plist",
    "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>CFBundleExecutable</key>\n\t<string>libGodotFmod.macos.template_debug</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>com.utopiarise.fmod-gdextension</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>libGodotFmod.macos.template_debug</string>\n\t<key>CFBundlePackageType</key>\n\t<string>FMWK</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0.0</string>\n\t<key>CFBundleSupportedPlatforms</key>\n\t<array>\n\t\t<string>MacOSX</string>\n\t</array>\n\t<key>CFBundleVersion</key>\n\t<string>1.0.0</string>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>10.12</string>\n</dict>\n</plist>"
  },
  {
    "path": "demo/addons/fmod/libs/macos/libGodotFmod.macos.template_release.framework/Info.plist",
    "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>CFBundleExecutable</key>\n\t<string>libGodotFmod.macos.template_release</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>com.utopiarise.fmod-gdextension</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>libGodotFmod.macos.template_release</string>\n\t<key>CFBundlePackageType</key>\n\t<string>FMWK</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0.0</string>\n\t<key>CFBundleSupportedPlatforms</key>\n\t<array>\n\t\t<string>MacOSX</string>\n\t</array>\n\t<key>CFBundleVersion</key>\n\t<string>1.0.0</string>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>10.12</string>\n</dict>\n</plist>"
  },
  {
    "path": "demo/addons/fmod/libs/windows/CopyPast_Fmod_Libs_Here.txt",
    "content": ""
  },
  {
    "path": "demo/addons/fmod/plugin.cfg",
    "content": "[plugin]\n\nname=\"FMOD GDExtension\"\ndescription=\"FMOD GDExtension for Godot Engine 4.5\"\nauthor=\"Utopia-rise, Tristan Grespinet, Pierre-Thomas Meisels\"\nversion=\"6.1.0\"\nscript=\"FmodPlugin.gd\"\n"
  },
  {
    "path": "demo/addons/fmod/tool/FmodBankDatabase.gd",
    "content": "extends Node\n\nclass_name FmodBankDatabase\n\n\nstatic var banks := Array()\nconst MASTER_STRINGS_BANK_NAME = \"Master.strings.bank\"\nconst MASTER_BANK_NAME = \"Master.bank\"\n\nstatic func reload_all_banks(): \n\tbanks.clear()\n\t\n\tvar banks_root = ProjectSettings.get_setting(\"Fmod/General/banks_path\", \"\")\n\tvar master_strings_bank_path = \"%s/%s\" % [banks_root, MASTER_STRINGS_BANK_NAME]\n\tvar master_bank_path = \"%s/%s\" % [banks_root, MASTER_BANK_NAME]\n\t\n\tif not FileAccess.file_exists(master_strings_bank_path):\n\t\tpush_warning(\"Cannot find master strings bank at %s\" % master_strings_bank_path)\n\t\treturn\n\t\n\tif not FileAccess.file_exists(master_bank_path):\n\t\tpush_warning(\"Cannot find master bank at %s\" % master_bank_path)\n\t\treturn\n\t\n\tbanks.append(\n\t\tFmodServer.load_bank(master_strings_bank_path, FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t)\n\tbanks.append(\n\t\tFmodServer.load_bank(master_bank_path, FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t)\n\n\tvar dir: DirAccess = DirAccess.open(banks_root)\n\tif dir:\n\t\tdir.list_dir_begin()\n\t\tvar file_name : String = dir.get_next()\n\t\twhile file_name != \"\":\n\t\t\tif dir.current_is_dir():\n\t\t\t\tpass # the found item is a directory\n\t\t\telif file_name.ends_with(\".bank\") and file_name != MASTER_STRINGS_BANK_NAME and file_name != MASTER_BANK_NAME:\n\t\t\t\tbanks.append(\n\t\t\t\t\tFmodServer.load_bank(\"%s/%s\" % [banks_root, file_name], FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t\t\t)\n\t\t\tfile_name = dir.get_next()\n\t\n"
  },
  {
    "path": "demo/addons/fmod/tool/FmodBankDatabase.gd.uid",
    "content": "uid://dovuikgbkylhi\n"
  },
  {
    "path": "demo/addons/fmod/tool/inspectors/FmodBankLoaderPropertyInspectorPlugin.gd",
    "content": "class_name FmodBankLoaderPropertyInspectorPlugin extends EditorInspectorPlugin\n\nstatic var bank_icon = load(\"res://addons/fmod/icons/bank_icon.svg\")\n\nvar _open_project_explorer_callable: Callable\n\nfunc _init(plugin: FmodPlugin):\n\t_open_project_explorer_callable = plugin.open_project_explorer_bank\n\nfunc _can_handle(object: Object):\n\treturn object is FmodBankLoader\n\nfunc _parse_property(object: Object, type: Variant.Type, name: String, hint_type: PropertyHint, hint_string: String, usage_flags: int, wide: bool):\n\treturn name == \"bank_paths\"\n\nfunc _parse_category(object: Object, category: String):\n\tif category != \"FmodBankLoader\":\n\t\treturn\n\tvar editor_property := FmodBankPathEditorProperty.new(_open_project_explorer_callable)\n\tadd_property_editor(\"bank_paths\", editor_property, false, \"Fmod banks\")\n"
  },
  {
    "path": "demo/addons/fmod/tool/inspectors/FmodBankLoaderPropertyInspectorPlugin.gd.uid",
    "content": "uid://tnljjxahubam\n"
  },
  {
    "path": "demo/addons/fmod/tool/inspectors/FmodEmitterPropertyInspectorPlugin.gd",
    "content": "class_name FmodEmitterPropertyInspectorPlugin extends EditorInspectorPlugin\n\nvar _open_project_explorer_callable: Callable\nvar _event_editor_property_scene: PackedScene = load(\"res://addons/fmod/tool/property_editors/FmodEventEditorProperty.tscn\")\n\nfunc _init(plugin: FmodPlugin):\n\t_open_project_explorer_callable = plugin.open_project_explorer_events\n\nfunc _can_handle(object: Object):\n\treturn object is FmodEventEmitter2D or \\\n\tobject is FmodEventEmitter3D\n\nfunc _parse_property(object: Object, type: Variant.Type, name: String, hint_type: PropertyHint, hint_string: String, usage_flags: int, wide: bool):\n\treturn name == \"event_name\" || name == \"event_guid\"\n\nfunc _parse_category(object: Object, category: String):\n\tif category != \"FmodEventEmitter2D\" and category != \"FmodEventEmitter3D\":\n\t\treturn\n\tvar editor_property := _event_editor_property_scene.instantiate()\n\teditor_property.initialize(_open_project_explorer_callable, \"event_name\", \"event_guid\")\n\tadd_property_editor_for_multiple_properties(\"Fmod event\", PackedStringArray([\"event_name\", \"event_guid\"]), editor_property)\n"
  },
  {
    "path": "demo/addons/fmod/tool/inspectors/FmodEmitterPropertyInspectorPlugin.gd.uid",
    "content": "uid://co1ktq45h26wx\n"
  },
  {
    "path": "demo/addons/fmod/tool/performances/PerformancesDisplay.gd",
    "content": "class_name PerformancesDisplay extends Node\n\n\nconst CORE_CPU_DSP_CATEGORY = \"FMOD [Core]/Cpu DSP\"\nconst CORE_CPU_GEOMETRY_CATEGORY = \"FMOD [Core]/Cpu Geometry\"\nconst CORE_CPU_STREAM_CATEGORY = \"FMOD [Core]/Cpu Stream\"\nconst CORE_CPU_UPDATE_CATEGORY = \"FMOD/[Core] Cpu Update\"\nconst CORE_CPU_CONVOLUTION_THREAD1_CATEGORY = \"FMOD/[Core] Cpu convolution Thread 1\"\nconst CORE_CPU_CONVOLUTION_THREAD2_CATEGORY = \"FMOD/[Core] Cpu convolution Thread 2\"\n\nconst STUDIO_CPU_UPDATE_CATEGORY = \"FMOD/[Studio] Cpu Update\"\n\nconst MEMORY_CURRENTLY_ALLOCATED_CATEGORY = \"FMOD/[Memory] Currently allocated\"\nconst MEMORY_MAX_ALLOCATED_CATEGORY = \"FMOD/[Memory] Max allocated\"\n\nconst FILE_SAMPLE_CATEGORY = \"FMOD/[File] Sample bytes read\"\nconst FILE_STREAM_CATEGORY = \"FMOD/[File] Stream bytes read\"\nconst FILE_OTHER_CATEGORY = \"FMOD/[File] Other bytes read\"\n\nfunc _enter_tree():\n\tvar performance_data: FmodPerformanceData = FmodServer.get_performance_data()\n\tadd_monitor(CORE_CPU_DSP_CATEGORY, func(): return performance_data.dsp)\n\tadd_monitor(CORE_CPU_GEOMETRY_CATEGORY, func(): return performance_data.geometry)\n\tadd_monitor(CORE_CPU_STREAM_CATEGORY, func(): return performance_data.stream)\n\tadd_monitor(CORE_CPU_UPDATE_CATEGORY, func(): return performance_data.update)\n\tadd_monitor(CORE_CPU_CONVOLUTION_THREAD1_CATEGORY, func(): return performance_data.convolution1)\n\tadd_monitor(CORE_CPU_CONVOLUTION_THREAD2_CATEGORY, func(): return performance_data.convolution2)\n\t\n\tadd_monitor(STUDIO_CPU_UPDATE_CATEGORY, func(): return performance_data.studio)\n\t\n\tadd_monitor(MEMORY_CURRENTLY_ALLOCATED_CATEGORY, func(): return performance_data.currently_allocated)\n\tadd_monitor(MEMORY_MAX_ALLOCATED_CATEGORY, func(): return performance_data.max_allocated)\n\t\n\tadd_monitor(FILE_SAMPLE_CATEGORY, func(): return performance_data.sample_bytes_read)\n\tadd_monitor(FILE_STREAM_CATEGORY, func(): return performance_data.stream_bytes_read)\n\tadd_monitor(FILE_OTHER_CATEGORY, func(): return performance_data.other_bytes_read)\n\nfunc _exit_tree() -> void:\n\tremove_monitor(CORE_CPU_DSP_CATEGORY)\n\tremove_monitor(CORE_CPU_GEOMETRY_CATEGORY)\n\tremove_monitor(CORE_CPU_STREAM_CATEGORY)\n\tremove_monitor(CORE_CPU_UPDATE_CATEGORY)\n\tremove_monitor(CORE_CPU_CONVOLUTION_THREAD1_CATEGORY)\n\tremove_monitor(CORE_CPU_CONVOLUTION_THREAD2_CATEGORY)\n\t\n\tremove_monitor(STUDIO_CPU_UPDATE_CATEGORY)\n\t\n\tremove_monitor(MEMORY_CURRENTLY_ALLOCATED_CATEGORY)\n\tremove_monitor(MEMORY_MAX_ALLOCATED_CATEGORY)\n\t\n\tremove_monitor(FILE_SAMPLE_CATEGORY)\n\tremove_monitor(FILE_STREAM_CATEGORY)\n\tremove_monitor(FILE_OTHER_CATEGORY)\n\nfunc add_monitor(title: String, callable: Callable) -> void:\n\tif not Performance.has_custom_monitor(title):\n\t\tPerformance.add_custom_monitor(title, callable)\n\nfunc remove_monitor(title: String) -> void:\n\tif Performance.has_custom_monitor(title):\n\t\tPerformance.remove_custom_monitor(title)\n"
  },
  {
    "path": "demo/addons/fmod/tool/performances/PerformancesDisplay.gd.uid",
    "content": "uid://bc0uajlvc0u00\n"
  },
  {
    "path": "demo/addons/fmod/tool/property_editors/FmodBankPathEditorProperty.gd",
    "content": "class_name FmodBankPathEditorProperty extends EditorProperty\n\nvar path_property_name := \"bank_paths\"\n\nvar ui: Control\nvar last_selected_index := -1\n\nfunc _init(open_project_explorer_callable: Callable):\n\tui = load(\"res://addons/fmod/tool/property_editors/FmodBankPathsPropertyEditorUi.tscn\").instantiate()\n\tadd_child(ui)\n\tvar add_button: Button = ui.get_node(\"%AddButton\")\n\t\n\tvar open_project_explorer_event = func open_project_explorer_event():\n\t\topen_project_explorer_callable.call(self._set_path_and_guid)\n\tadd_button.pressed.connect(open_project_explorer_event)\n\t\n\tvar remove_button: Button = ui.get_node(\"%RemoveButton\")\n\tremove_button.pressed.connect(_on_remove_button)\n\t\n\tvar manual_add_button: Button = ui.get_node(\"%ManualAddButton\")\n\tmanual_add_button.pressed.connect(_on_manual_add_button)\n\t\n\tvar up_button: Button = ui.get_node(\"%UpButton\")\n\tup_button.pressed.connect(_on_move_button.bind(false))\n\t\n\tvar down_button: Button = ui.get_node(\"%DownButton\")\n\tdown_button.pressed.connect(_on_move_button.bind(true))\n\nfunc _update_property():\n\tvar bank_list: ItemList = ui.get_node(\"%BankList\")\n\tbank_list.clear()\n\tvar bank_paths: Array = get_edited_object()[path_property_name]\n\tfor path in bank_paths:\n\t\tbank_list.add_item(path)\n\t\n\tif last_selected_index == -1:\n\t\treturn\n\tbank_list.select(last_selected_index)\n\tlast_selected_index = -1\n\nfunc _set_path_and_guid(path: String, _cancel: String):\n\tvar current_bank_paths: Array = get_edited_object()[path_property_name]\n\t\n\tif current_bank_paths.has(path):\n\t\treturn\n\t\n\tvar bank_paths := Array(current_bank_paths)\n\tbank_paths.append(path)\n\temit_changed(path_property_name, bank_paths)\n\nfunc _on_remove_button():\n\tvar bank_list: ItemList = ui.get_node(\"%BankList\")\n\tvar current_bank_paths: Array = get_edited_object()[path_property_name]\n\tvar bank_paths := Array(current_bank_paths)\n\tvar selected_items_indexes: PackedInt32Array = bank_list.get_selected_items()\n\tif selected_items_indexes.is_empty():\n\t\treturn\n\tvar item = bank_list.get_item_text(selected_items_indexes[0])\n\tvar in_list_index = bank_paths.find(item)\n\tbank_paths.remove_at(in_list_index)\n\tlast_selected_index = in_list_index if in_list_index < bank_paths.size() else in_list_index - 1\n\temit_changed(path_property_name, bank_paths)\n\nfunc _on_manual_add_button():\n\tvar manual_add_line_edit: LineEdit = ui.get_node(\"%ManualAddLineEdit\")\n\tvar to_add: String = manual_add_line_edit.text\n\tif not to_add.begins_with(\"res://\") || not to_add.ends_with(\".bank\"):\n\t\treturn\n\t_set_path_and_guid(to_add, \"\")\n\tmanual_add_line_edit.text = \"\"\n\nfunc _on_move_button(is_down: bool):\n\tvar bank_list: ItemList = ui.get_node(\"%BankList\")\n\tvar current_bank_paths: Array = get_edited_object()[path_property_name]\n\tvar bank_paths := Array(current_bank_paths)\n\tvar selected_items_indexes: PackedInt32Array = bank_list.get_selected_items()\n\tif selected_items_indexes.is_empty():\n\t\treturn\n\tvar item = bank_list.get_item_text(selected_items_indexes[0])\n\tvar in_list_index = bank_paths.find(item)\n\tvar boundary = current_bank_paths.size() - 1 if is_down else 0\n\tif in_list_index == boundary:\n\t\treturn\n\tvar new_index = in_list_index + 1 if is_down else in_list_index - 1\n\tbank_paths.remove_at(in_list_index)\n\tbank_paths.insert(new_index, item)\n\tlast_selected_index = new_index\n\temit_changed(path_property_name, bank_paths)\n"
  },
  {
    "path": "demo/addons/fmod/tool/property_editors/FmodBankPathEditorProperty.gd.uid",
    "content": "uid://cxyd4qioylvgr\n"
  },
  {
    "path": "demo/addons/fmod/tool/property_editors/FmodBankPathsPropertyEditorUi.tscn",
    "content": "[gd_scene load_steps=2 format=3 uid=\"uid://dtlwk8wdeal3h\"]\n\n[ext_resource type=\"Texture2D\" uid=\"uid://o2chsr07oeqs\" path=\"res://addons/fmod/icons/bank_icon.svg\" id=\"1_11c48\"]\n\n[node name=\"FmodBankPathsPropertyEditorUi\" type=\"VBoxContainer\"]\noffset_right = 92.0\noffset_bottom = 43.0\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n\n[node name=\"HBoxContainer\" type=\"HBoxContainer\" parent=\".\"]\nlayout_mode = 2\n\n[node name=\"AddButton\" type=\"Button\" parent=\"HBoxContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_horizontal = 3\ntext = \"+\"\nicon = ExtResource(\"1_11c48\")\nicon_alignment = 2\n\n[node name=\"RemoveButton\" type=\"Button\" parent=\"HBoxContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_horizontal = 3\ntext = \"-\"\n\n[node name=\"VSeparator\" type=\"VSeparator\" parent=\"HBoxContainer\"]\nlayout_mode = 2\n\n[node name=\"UpButton\" type=\"Button\" parent=\"HBoxContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_horizontal = 3\ntext = \"↑\"\n\n[node name=\"DownButton\" type=\"Button\" parent=\"HBoxContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_horizontal = 3\ntext = \"↓\"\n\n[node name=\"BankList\" type=\"ItemList\" parent=\".\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_vertical = 3\nauto_height = true\n\n[node name=\"HBoxContainer2\" type=\"HBoxContainer\" parent=\".\"]\nlayout_mode = 2\n\n[node name=\"ManualAddLineEdit\" type=\"LineEdit\" parent=\"HBoxContainer2\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"ManualAddButton\" type=\"Button\" parent=\"HBoxContainer2\"]\nunique_name_in_owner = true\nlayout_mode = 2\ntext = \"+\"\n"
  },
  {
    "path": "demo/addons/fmod/tool/property_editors/FmodEventEditorProperty.gd",
    "content": "@tool class_name FmodEventEditorProperty extends FmodPathEditorProperty\n\n\nstatic var EVENT_PARAMETER_PREFIX_FOR_PROPERTIES = \"fmod_parameters\"\n\nvar former_event_description: FmodEventDescription\n\nfunc _update_property():\n\tsuper()\n\tif get_edited_object().event_name == \"\":\n\t\treturn\n\t_update_parameters()\n\tvar event_description: FmodEventDescription = FmodServer.get_event_from_guid(get_edited_object().event_guid)\n\t\n\tif event_description == null:\n\t\tevent_description = FmodServer.get_event(get_edited_object().event_name)\n\t\n\tformer_event_description = event_description\n\nfunc _set_path_and_guid(path: String, guid: String):\n\tsuper(path, guid)\n\tif get_edited_object().event_name == \"\":\n\t\treturn\n\t_update_parameters()\n\tformer_event_description = FmodServer.get_event_from_guid(get_edited_object().event_guid)\n\nfunc _update_parameters():\n\tvar event_description: FmodEventDescription = FmodServer.get_event_from_guid(get_edited_object().event_guid)\n\t\n\tif event_description == null:\n\t\treturn\n\t\n\tif former_event_description != null and event_description != former_event_description:\n\t\tget_edited_object().tool_remove_all_parameters()\n\t\n\tvar map_to_property_name = func map_to_property_name(dict: Dictionary):\n\t\t\treturn dict[\"name\"]\n\tvar filter_fmod_parameter_property = func filter_fmod_parameter_property(parameter_name: String):\n\t\treturn parameter_name.begins_with(EVENT_PARAMETER_PREFIX_FOR_PROPERTIES)\n\t\n\tvar filter_property_id = func filter_property_id(property: String):\n\t\treturn property.ends_with(\"/id\")\n\tvar existing_property_ids = get_edited_object().get_property_list().map(map_to_property_name).filter(filter_fmod_parameter_property).filter(filter_property_id)\n\t\n\tvar map_property_name_to_parameter_name = func map_property_name_to_parameter_name(parameter: String):\n\t\treturn parameter.split(\"/\")[1]\n\tvar existing_parameter_names = existing_property_ids.map(map_property_name_to_parameter_name)\n\t\n\tvar map_property_id_to_parameter_id_value = func map_property_id_to_parameter_id_value(property: String):\n\t\treturn get_edited_object()[property]\n\tvar existing_parameter_ids = existing_property_ids.map(map_property_id_to_parameter_id_value)\n\t\n\tvar property_matching = existing_parameter_ids.map(func(id): return false)\n\t\n\tfor param: FmodParameterDescription in event_description.get_parameters():\n\t\tif param.is_global() or param.is_automatic() or param.is_read_only():\n\t\t\tcontinue\n\t\t\n\t\tvar parameter_name = param.get_name()\n\t\tvar parameter_id_param = \"%s/%s/id\" % [EVENT_PARAMETER_PREFIX_FOR_PROPERTIES, parameter_name]\n\t\tvar parameter_value_param = \"%s/%s\" % [EVENT_PARAMETER_PREFIX_FOR_PROPERTIES, parameter_name]\n\t\tvar parameter_variant_type = \"%s/%s/variant_type\" % [EVENT_PARAMETER_PREFIX_FOR_PROPERTIES, parameter_name]\n\t\tvar parameter_labels = \"%s/%s/labels\" % [EVENT_PARAMETER_PREFIX_FOR_PROPERTIES, parameter_name]\n\t\t\n\t\tvar existing_property_name_index = existing_property_ids.find(parameter_id_param)\n\t\tvar are_properties_already_in_node = existing_property_name_index != -1\n\t\t\n\t\tvar parameter_id = param.get_id()\n\t\t\n\t\tvar variant_type: Variant.Type = TYPE_FLOAT\n\t\tvar default_value = param.get_default_value()\n\t\tvar minimum_value = param.get_minimum()\n\t\tvar maximum_value = param.get_maximum()\n\t\tif param.is_labeled():\n\t\t\tvariant_type = TYPE_STRING\n\t\t\tdefault_value = event_description.get_parameter_label_by_id(parameter_id, default_value)\n\t\t\tminimum_value = event_description.get_parameter_label_by_id(parameter_id, minimum_value)\n\t\t\tmaximum_value = event_description.get_parameter_label_by_id(parameter_id, maximum_value)\n\t\telif param.is_discrete():\n\t\t\tvariant_type = TYPE_INT\n\t\t\tdefault_value = int(default_value)\n\t\t\tminimum_value = int(minimum_value)\n\t\t\tmaximum_value = int(maximum_value)\n\t\t\n\t\tif are_properties_already_in_node:\n\t\t\tproperty_matching[existing_property_name_index] = existing_parameter_ids[existing_property_name_index] == parameter_id\n\t\t\n\t\tif not are_properties_already_in_node or get_edited_object()[parameter_id_param] == null:\n\t\t\tget_edited_object()[parameter_id_param] = parameter_id\n\t\tif not are_properties_already_in_node or get_edited_object()[parameter_value_param] == null:\n\t\t\tget_edited_object()[parameter_value_param] = default_value\n\t\tif not are_properties_already_in_node or get_edited_object()[parameter_variant_type] == null:\n\t\t\tget_edited_object()[parameter_variant_type] = variant_type\n\t\tif param.is_labeled() and (not are_properties_already_in_node or get_edited_object()[parameter_labels] == null):\n\t\t\tget_edited_object()[parameter_labels] = event_description.get_parameter_labels_by_id(parameter_id)\n\t\n\tfor i in property_matching.size():\n\t\tif not property_matching[i]:\n\t\t\tget_edited_object().tool_remove_parameter(existing_parameter_ids[i])\n\t\n\tget_edited_object().notify_property_list_changed()\n"
  },
  {
    "path": "demo/addons/fmod/tool/property_editors/FmodEventEditorProperty.gd.uid",
    "content": "uid://b32x60k0th8td\n"
  },
  {
    "path": "demo/addons/fmod/tool/property_editors/FmodEventEditorProperty.tscn",
    "content": "[gd_scene load_steps=3 format=3 uid=\"uid://cowfthogh1n7i\"]\n\n[ext_resource type=\"PackedScene\" uid=\"uid://cujo3xq0erren\" path=\"res://addons/fmod/tool/property_editors/FmodPathEditorProperty.tscn\" id=\"1_xvpec\"]\n[ext_resource type=\"Script\" uid=\"uid://b32x60k0th8td\" path=\"res://addons/fmod/tool/property_editors/FmodEventEditorProperty.gd\" id=\"2_nkhkm\"]\n\n[node name=\"FmodEventEditorProperty\" instance=ExtResource(\"1_xvpec\")]\nscript = ExtResource(\"2_nkhkm\")\n"
  },
  {
    "path": "demo/addons/fmod/tool/property_editors/FmodGuidAndPathPropertyEditorUi.gd",
    "content": "@tool class_name FmodGuidAndPathPropertyEditorUi extends HBoxContainer\n\n\nfunc set_callables(window_callable: Callable, path_callable: Callable, guid_callable: Callable):\n\t%EventExplorerButton.pressed.connect(window_callable)\n\t%PathLineEdit.text_changed.connect(path_callable)\n\t%GuidLineEdit.text_changed.connect(guid_callable)\n\nfunc set_icon(icon: Texture2D):\n\t%EventExplorerButton.icon = icon\n"
  },
  {
    "path": "demo/addons/fmod/tool/property_editors/FmodGuidAndPathPropertyEditorUi.gd.uid",
    "content": "uid://3xn18ci172v4\n"
  },
  {
    "path": "demo/addons/fmod/tool/property_editors/FmodGuidAndPathPropertyEditorUi.tscn",
    "content": "[gd_scene load_steps=3 format=3 uid=\"uid://hy04frgfhtgu\"]\n\n[ext_resource type=\"Script\" uid=\"uid://3xn18ci172v4\" path=\"res://addons/fmod/tool/property_editors/FmodGuidAndPathPropertyEditorUi.gd\" id=\"1_eao7t\"]\n[ext_resource type=\"Texture2D\" uid=\"uid://cmpgmbn3y4svl\" path=\"res://addons/fmod/icons/event_icon.svg\" id=\"1_kuu6i\"]\n\n[node name=\"FmodGuidAndPathPropertyEditorUi\" type=\"HBoxContainer\"]\noffset_right = 1152.0\noffset_bottom = 66.0\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\nscript = ExtResource(\"1_eao7t\")\n\n[node name=\"VBoxContainer\" type=\"VBoxContainer\" parent=\".\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"PathLineEdit\" type=\"LineEdit\" parent=\"VBoxContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\n\n[node name=\"GuidLineEdit\" type=\"LineEdit\" parent=\"VBoxContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\n\n[node name=\"EventExplorerButton\" type=\"Button\" parent=\".\"]\nunique_name_in_owner = true\nlayout_mode = 2\nicon = ExtResource(\"1_kuu6i\")\n"
  },
  {
    "path": "demo/addons/fmod/tool/property_editors/FmodPathEditorProperty.gd",
    "content": "@tool class_name FmodPathEditorProperty extends EditorProperty\n\nvar ui: Control\nvar guid_property: String\nvar path_property: String\nvar regex := RegEx.new()\n\nvar default_line_edit_modulate: Color\n\nfunc initialize(open_project_explorer_callable: Callable, path_prop: String, guid_prop: String):\n\tregex.compile(\"{\\\\w{8}-\\\\w{4}-\\\\w{4}-\\\\w{4}-\\\\w{12}}\")\n\tguid_property = guid_prop\n\tpath_property = path_prop\n\tvar guid_and_path_ui: FmodGuidAndPathPropertyEditorUi = %FmodGuidAndPathPropertyEditorUi\n\t\n\tdefault_line_edit_modulate = guid_and_path_ui.get_node(\"%GuidLineEdit\").modulate\n\t\n\tvar open_project_explorer_event = func open_project_explorer_event():\n\t\topen_project_explorer_callable.call(self._set_path_and_guid)\n\tguid_and_path_ui.set_callables(open_project_explorer_event, _set_path, _set_guid)\n\nfunc _update_property():\n\tvar guid_and_path_ui = %FmodGuidAndPathPropertyEditorUi\n\tguid_and_path_ui.get_node(\"%PathLineEdit\").text = get_edited_object()[path_property]\n\tguid_and_path_ui.get_node(\"%GuidLineEdit\").text = get_edited_object()[guid_property]\n\nfunc _set_path(path: String):\n\temit_changed(path_property, path)\n\nfunc _set_guid(guid: String):\n\tvar line_edit := %FmodGuidAndPathPropertyEditorUi.get_node(\"%GuidLineEdit\") as LineEdit\n\tif not regex.search(guid):\n\t\tline_edit.modulate = Color.RED\n\t\treturn\n\tline_edit.modulate = default_line_edit_modulate\n\temit_changed(guid_property, guid)\n\nfunc _set_path_and_guid(path: String, guid: String):\n\t_set_path(path)\n\t_set_guid(guid)\n"
  },
  {
    "path": "demo/addons/fmod/tool/property_editors/FmodPathEditorProperty.gd.uid",
    "content": "uid://qshng8csi2fr\n"
  },
  {
    "path": "demo/addons/fmod/tool/property_editors/FmodPathEditorProperty.tscn",
    "content": "[gd_scene load_steps=3 format=3 uid=\"uid://cujo3xq0erren\"]\n\n[ext_resource type=\"Script\" uid=\"uid://qshng8csi2fr\" path=\"res://addons/fmod/tool/property_editors/FmodPathEditorProperty.gd\" id=\"1_4e4vx\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://hy04frgfhtgu\" path=\"res://addons/fmod/tool/property_editors/FmodGuidAndPathPropertyEditorUi.tscn\" id=\"2_nvtqg\"]\n\n[node name=\"FmodPathEditorProperty\" type=\"EditorProperty\"]\nscript = ExtResource(\"1_4e4vx\")\n\n[node name=\"VBoxContainer\" type=\"VBoxContainer\" parent=\".\"]\nlayout_mode = 2\n\n[node name=\"FmodGuidAndPathPropertyEditorUi\" parent=\"VBoxContainer\" instance=ExtResource(\"2_nvtqg\")]\nunique_name_in_owner = true\nlayout_mode = 2\n"
  },
  {
    "path": "demo/addons/fmod/tool/ui/EventParametersDisplay.gd",
    "content": "@tool class_name EventParametersDisplay extends ScrollContainer\n\nstatic var parameter_display_scene: PackedScene = load(\"res://addons/fmod/tool/ui/ParameterDisplay.tscn\")\n\nfunc set_fmod_event(event: FmodEventDescription) -> bool: # returns false if there were no parameters\n\tfor child in %ParameterDisplaysContainer.get_children():\n\t\t%ParameterDisplaysContainer.remove_child(child)\n\t\tchild.queue_free()\n\t\n\tvar event_parameters: Array = event.get_parameters()\n\tif event_parameters:\n\t\tshow()\n\t\tfor parameter : FmodParameterDescription in event_parameters:\n\t\t\tvar parameter_display: ParameterDisplay = parameter_display_scene.instantiate()\n\t\t\tparameter_display.set_event_description(event)\n\t\t\tparameter_display.set_parameter(parameter)\n\t\t\tif %ParameterDisplaysContainer.get_child_count() > 0:\n\t\t\t\t%ParameterDisplaysContainer.add_child(HSeparator.new())\n\t\t\t%ParameterDisplaysContainer.add_child(parameter_display)\n\t\treturn true # we had parameters to show!\n\telse:\n\t\treturn false # no parameters to visualise\n"
  },
  {
    "path": "demo/addons/fmod/tool/ui/EventParametersDisplay.gd.uid",
    "content": "uid://7relkis52fsu\n"
  },
  {
    "path": "demo/addons/fmod/tool/ui/EventParametersDisplay.tscn",
    "content": "[gd_scene load_steps=2 format=3 uid=\"uid://cppeyr1ke5wre\"]\n\n[ext_resource type=\"Script\" uid=\"uid://7relkis52fsu\" path=\"res://addons/fmod/tool/ui/EventParametersDisplay.gd\" id=\"1_2l58q\"]\n\n[node name=\"EventParametersDisplay\" type=\"ScrollContainer\"]\nanchors_preset = 15\nanchor_right = 1.0\nanchor_bottom = 1.0\ngrow_horizontal = 2\ngrow_vertical = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\nscript = ExtResource(\"1_2l58q\")\n\n[node name=\"ParameterDisplaysContainer\" type=\"VBoxContainer\" parent=\".\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n"
  },
  {
    "path": "demo/addons/fmod/tool/ui/EventParametersWindow.tscn",
    "content": "[gd_scene load_steps=2 format=3 uid=\"uid://skp8awewyl85\"]\n\n[ext_resource type=\"PackedScene\" uid=\"uid://cppeyr1ke5wre\" path=\"res://addons/fmod/tool/ui/EventParametersDisplay.tscn\" id=\"1_clkxg\"]\n\n[node name=\"EventParametersWindow\" type=\"Window\"]\n\n[node name=\"EventParametersDisplay\" parent=\".\" instance=ExtResource(\"1_clkxg\")]\n"
  },
  {
    "path": "demo/addons/fmod/tool/ui/EventPlayControls.gd",
    "content": "@tool\nextends PanelContainer\n\n@export var play_button : Button\n@export var stop_button : Button\n@export var fade_out_toggle : CheckButton\nvar event_instance : FmodEvent\n\nfunc _ready() -> void:\n\thide()\n\tplay_button.icon = EditorInterface.get_editor_theme().get_icon(\"Play\", \"EditorIcons\")\n\tstop_button.icon = EditorInterface.get_editor_theme().get_icon(\"Stop\", \"EditorIcons\")\n\tplay_button.pressed.connect(on_play_button_pressed)\n\tstop_button.pressed.connect(on_stop_button_pressed)\n\n\nfunc set_fmod_event(_event_description : FmodEventDescription) -> void:\n\tstop_and_release_instance() # always stop and release if a previous one is active\n\tevent_instance = FmodServer.create_event_instance_with_guid(_event_description.get_guid())\n\tshow()\n\t\n\t\nfunc play_event() -> void:\n\tif event_instance:\n\t\tevent_instance.start()\n\t\n\t\nfunc stop_event() -> void:\n\tif event_instance:\n\t\tvar stop_mode : int = FmodServer.FMOD_STUDIO_STOP_IMMEDIATE\n\t\tif fade_out_toggle.button_pressed:\n\t\t\tstop_mode = FmodServer.FMOD_STUDIO_STOP_ALLOWFADEOUT\n\t\t\t\n\t\tevent_instance.stop(stop_mode)\n\t\n\t\nfunc _exit_tree() -> void:\n\tstop_and_release_instance()\n\t\n\t\nfunc stop_and_release_instance() -> void:\n\tif event_instance:\n\t\tevent_instance.stop(0)\n\t\tevent_instance.release()\n\n\nfunc on_play_button_pressed() -> void:\n\tplay_event()\n\t\n\t\nfunc on_stop_button_pressed() -> void:\n\tstop_event()\n"
  },
  {
    "path": "demo/addons/fmod/tool/ui/EventPlayControls.gd.uid",
    "content": "uid://vgmq7hfrbddw\n"
  },
  {
    "path": "demo/addons/fmod/tool/ui/FmodBankExplorer.gd",
    "content": "@tool class_name FmodBankExplorer extends Window\n\nenum ToDisplayFlags {\n\tBUSES = 1,\n\tVCA = 2,\n\tEVENTS = 4\n}\n\nstatic var _fmod_icon = load(\"res://addons/fmod/icons/fmod_icon.svg\")\nstatic var _vca_icon = load(\"res://addons/fmod/icons/vca_icon.svg\")\nstatic var _bank_icon = load(\"res://addons/fmod/icons/bank_icon.svg\")\nstatic var _event_icon = load(\"res://addons/fmod/icons/event_icon.svg\")\nstatic var _bus_icon = load(\"res://addons/fmod/icons/bus_icon.svg\")\nstatic var _snapshot_icon = load(\"res://addons/fmod/icons/snapshot_icon.svg\")\n\nsignal emit_path_and_guid(path: String, guid: String)\n\nvar tree: Tree\n@onready var copy_path_button := %PathLabel.get_child(0)\n@onready var copy_guid_button := %GuidLabel.get_child(0)\n\nvar should_display_copy_buttons = true\nvar should_display_select_button = false\n\nvar _current_select_callable: Callable\n\nvar base_color: Color\nvar contrast: float\nvar background_color: Color\n\nvar banks: Array = Array()\n\nvar flags: int = 0\nvar search: String = \"\"\n\nfunc reset_search():\n\t%SearchField.text = \"\"\n\tsearch = \"\"\n\nfunc _ready():\n\tvar main_window_size = get_parent().get_window().size\n\tsize = main_window_size * 0.5\n\n\tvar copy_texture : Texture = EditorInterface.get_editor_theme().get_icon(\"ActionCopy\", \"EditorIcons\")\n\tcopy_guid_button.icon = copy_texture\n\tcopy_path_button.icon = copy_texture\n\tcopy_guid_button.visible = false\n\tcopy_path_button.visible = false\n\tcopy_path_button.pressed.connect(_on_copy_path_button)\n\tcopy_guid_button.pressed.connect(_on_copy_guid_button)\n\n\tvar emit_path_and_guid_callable = func emit_path_and_guid_callable():\n\t\tvar selected_item = tree.get_selected()\n\t\tif selected_item == null:\n\t\t\treturn\n\t\tvar selected_fmod_element = selected_item.get_metadata(0)\n\t\tif selected_fmod_element == null:\n\t\t\treturn\n\n\t\tvar path = selected_fmod_element.get_godot_res_path() if selected_fmod_element is FmodBank else selected_fmod_element.get_path()\n\t\temit_path_and_guid.emit(path, selected_fmod_element.get_guid())\n\t%SelectButton.pressed.connect(emit_path_and_guid_callable)\n\t%SelectButton.pressed.connect(close_window)\n\t%CloseButton.pressed.connect(close_window)\n\tclose_requested.connect(close_window)\n\n\ttree = %Tree\n\ttree.item_selected.connect(_on_item_selected)\n\ttree.columns = 1\n\n\tgenerate_tree()\n\t%RefreshBanksButton.pressed.connect(on_refresh_banks_button_pressed)\n\n\nfunc regenerate_tree(callable: Callable = Callable()):\n\ttree.clear()\n\tgenerate_tree(callable)\n\nfunc generate_tree(callable: Callable = Callable()):\n\t%SelectButton.visible = should_display_select_button\n\tif _current_select_callable != Callable() && _current_select_callable.get_object() != null:\n\t\temit_path_and_guid.disconnect(_current_select_callable)\n\t_current_select_callable = callable\n\n\tvar root_item := tree.create_item()\n\troot_item.set_text(0, \"Fmod objects\")\n\troot_item.set_icon(0, _fmod_icon)\n\n\tvar has_many_flags = (flags & (flags - 1)) != 0\n\n\tfor bank in FmodServer.get_all_banks():\n\t\tvar fmod_bank := bank as FmodBank\n\n\t\tvar buses := fmod_bank.get_bus_list()\n\t\tvar vcas := fmod_bank.get_vca_list()\n\t\tvar events := fmod_bank.get_description_list()\n\n\t\tif search.is_empty():\n\t\t\tvar bank_item := tree.create_item(root_item)\n\t\t\tbank_item.set_text(0, fmod_bank.get_godot_res_path())\n\t\t\tbank_item.set_icon(0, _bank_icon)\n\t\t\tbank_item.set_metadata(0, bank)\n\n\t\t\tif flags & ToDisplayFlags.BUSES and buses.size() != 0:\n\t\t\t\tbuses.sort_custom(sort_by_path)\n\t\t\t\tif has_many_flags:\n\t\t\t\t\tvar buses_item := tree.create_item(bank_item)\n\t\t\t\t\tbuses_item.set_text(0, \"Buses\")\n\t\t\t\t\tbuses_item.set_icon(0, _bus_icon)\n\t\t\t\t\t_add_elements_as_tree(buses, buses_item)\n\t\t\t\telse:\n\t\t\t\t\t_add_elements_as_tree(buses, bank_item)\n\n\t\t\tif flags & ToDisplayFlags.VCA and vcas.size() != 0:\n\t\t\t\tvcas.sort_custom(sort_by_path)\n\t\t\t\tif has_many_flags:\n\t\t\t\t\tvar vca_item := tree.create_item(bank_item)\n\t\t\t\t\tvca_item.set_text(0, \"VCAs\")\n\t\t\t\t\tvca_item.set_icon(0, _vca_icon)\n\t\t\t\t\t_add_elements_as_tree(vcas, vca_item)\n\t\t\t\telse:\n\t\t\t\t\t_add_elements_as_tree(vcas, bank_item)\n\n\t\t\tif flags & ToDisplayFlags.EVENTS and events.size() != 0:\n\t\t\t\tevents.sort_custom(sort_by_path)\n\t\t\t\tif has_many_flags:\n\t\t\t\t\tvar events_item := tree.create_item(bank_item)\n\t\t\t\t\tevents_item.set_text(0, \"Events\")\n\t\t\t\t\tevents_item.set_icon(0, _event_icon)\n\t\t\t\t\t_add_elements_as_tree(events, events_item)\n\t\t\t\telse:\n\t\t\t\t\t_add_elements_as_tree(events, bank_item)\n\n\t\telse:\n\t\t\tif flags & ToDisplayFlags.BUSES:\n\t\t\t\t_add_elements_as_items(buses, root_item)\n\t\t\tif flags & ToDisplayFlags.VCA:\n\t\t\t\t_add_elements_as_items(vcas, root_item)\n\t\t\tif flags & ToDisplayFlags.EVENTS:\n\t\t\t\t_add_elements_as_items(events, root_item)\n\n\tif copy_path_button.visible:\n\t\tcopy_path_button.visible = should_display_copy_buttons\n\n\tif copy_guid_button.visible:\n\t\tcopy_guid_button.visible = should_display_copy_buttons\n\n\tif _current_select_callable != Callable():\n\t\tprint(_current_select_callable)\n\t\temit_path_and_guid.connect(_current_select_callable)\n\n\t%SelectButton.visible = should_display_select_button and %GuidLabel.text != \"\"\n\nfunc _add_elements_as_items(elements: Array, parent: TreeItem):\n\t\tfor element in elements:\n\t\t\tvar full_path: String = element.get_path()\n\t\t\tif not full_path.containsn(search):\n\t\t\t\tcontinue\n\n\t\t\tvar child := tree.create_item(parent)\n\t\t\tvar name := full_path.rsplit(\"/\")[-1]\n\t\t\tchild.set_text(0, name)\n\t\t\tchild.set_metadata(0, element)\n\t\t\tchild.set_icon(0, _get_icon_for_fmod_path(full_path))\n\nfunc _add_elements_as_tree(elements: Array, parent: TreeItem):\n\tvar nodes := { \"\": parent }\n\n\tfor element in elements:\n\t\tvar full_path: String = element.get_path()\n\t\tvar parts := full_path.split(\"/\")\n\t\t# Drop the “type:” prefix\n\t\tif parts.size() > 0:\n\t\t\tparts.remove_at(0)\n\n\t\t# Walk each segment in turn, building a running “key”\n\t\tvar key := \"\"\n\t\tfor i in range(parts.size()):\n\t\t\tvar name = parts[i]\n\t\t\tif full_path == \"bus:/\":\n\t\t\t\tname = \"Master\"\n\t\t\tif key == \"\":\n\t\t\t\tkey = name\n\t\t\telse:\n\t\t\t\tkey = key + \"/\" + name\n\n\t\t\t# If we haven’t created this node yet, do so now\n\t\t\tif not nodes.has(key):\n\t\t\t\tvar root_and_name := key.rsplit(\"/\", false, 1)\n\t\t\t\tvar parent_key: String = \"\"\n\t\t\t\tif root_and_name.size() == 2:\n\t\t\t\t\tparent_key = root_and_name[0]\n\t\t\t\tvar parent_item = nodes[parent_key]\n\t\t\t\tvar child := tree.create_item(parent_item)\n\t\t\t\tchild.set_text(0, name)\n\t\t\t\tnodes[key] = child\n\n\t\t\t# If this is the final segment, attach the metadata & icon\n\t\t\tif i == parts.size() - 1:\n\t\t\t\tvar leaf = nodes[key]\n\t\t\t\tleaf.set_metadata(0, element)\n\t\t\t\tleaf.set_icon(0, _get_icon_for_fmod_path(full_path))\n\nfunc _on_item_selected():\n\tvar metadata = tree.get_selected().get_metadata(0)\n\tif metadata == null or !metadata.get_guid():\n\t\t%PathsBG.hide()\n\t\t%EventPlayControls.hide()\n\t\tcopy_path_button.visible = false\n\t\tcopy_guid_button.visible = false\n\t\t%SelectButton.visible = false\n\t\t%ParametersLabel.visible = false\n\t\t%ParametersContainer.visible = false\n\t\treturn\n\t%GuidLabel.set_text(metadata.get_guid())\n\t%PathLabel.set_text(metadata.get_path())\n\t%PathsBG.show()\n\tif should_display_copy_buttons:\n\t\tcopy_path_button.visible = true\n\t\tcopy_guid_button.visible = true\n\tif should_display_select_button:\n\t\t%SelectButton.visible = true\n\n\tif metadata is FmodEventDescription:\n\t\t%EventPlayControls.set_fmod_event(metadata)\n\t\tvar _show_parameter_controls : bool = %EventParametersDisplay.set_fmod_event(metadata)\n\t\t%ParametersLabel.visible = _show_parameter_controls\n\t\t%ParametersContainer.visible = _show_parameter_controls\n\t\treturn\n\t%EventPlayControls.hide()\n\t%EventParametersDisplay.hide()\n\t%ParametersLabel.visible = false\n\t%ParametersContainer.visible = false\n\nfunc _on_copy_path_button():\n\tDisplayServer.clipboard_set(%PathLabel.text)\n\nfunc _on_copy_guid_button():\n\tDisplayServer.clipboard_set(%GuidLabel.text)\n\n\nfunc on_refresh_banks_button_pressed() -> void:\n\t# unload banks\n\tbanks.clear()\n\ttree.clear()\n\tFmodBankDatabase.reload_all_banks()\n\tgenerate_tree()\n\nfunc close_window():\n\t%EventPlayControls.stop_event()\n\tvisible = false\n\nstatic func _get_icon_for_fmod_path(fmod_path: String) -> Texture2D:\n\tvar icon: Texture2D = null\n\tif fmod_path.begins_with(\"bus:/\"):\n\t\ticon = _bus_icon\n\telif fmod_path.begins_with(\"event:/\"):\n\t\ticon = _event_icon\n\telif fmod_path.begins_with(\"vca:/\"):\n\t\ticon = _vca_icon\n\telif fmod_path.begins_with(\"snapshot:/\"):\n\t\ticon = _snapshot_icon\n\treturn icon\n\nstatic func sort_by_path(a, b):\n\treturn a.get_path().casecmp_to(b.get_path()) < 0\n\n\nfunc _on_text_edit_text_submitted(new_text: String) -> void:\n\tsearch = new_text\n\tregenerate_tree()\n"
  },
  {
    "path": "demo/addons/fmod/tool/ui/FmodBankExplorer.gd.uid",
    "content": "uid://b5xgbibc3amtk\n"
  },
  {
    "path": "demo/addons/fmod/tool/ui/FmodBankExplorer.tscn",
    "content": "[gd_scene load_steps=17 format=3 uid=\"uid://nr38urn226al\"]\n\n[ext_resource type=\"Script\" uid=\"uid://b5xgbibc3amtk\" path=\"res://addons/fmod/tool/ui/FmodBankExplorer.gd\" id=\"1_ekqus\"]\n[ext_resource type=\"Script\" uid=\"uid://vgmq7hfrbddw\" path=\"res://addons/fmod/tool/ui/EventPlayControls.gd\" id=\"2_mleop\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://cppeyr1ke5wre\" path=\"res://addons/fmod/tool/ui/EventParametersDisplay.tscn\" id=\"2_uoyg8\"]\n\n[sub_resource type=\"StyleBoxFlat\" id=\"StyleBoxFlat_potss\"]\nbg_color = Color(0.21176471, 0.23921569, 0.2901961, 1)\n\n[sub_resource type=\"StyleBoxFlat\" id=\"StyleBoxFlat_piloo\"]\nbg_color = Color(0.21176471, 0.23921569, 0.2901961, 1)\n\n[sub_resource type=\"Theme\" id=\"Theme_02ixt\"]\nButton/styles/normal = SubResource(\"StyleBoxFlat_potss\")\nLineEdit/styles/normal = SubResource(\"StyleBoxFlat_piloo\")\n\n[sub_resource type=\"StyleBoxFlat\" id=\"StyleBoxFlat_wrr0m\"]\nbg_color = Color(0, 0, 0, 0.247059)\nborder_width_left = 1\nborder_width_top = 1\nborder_width_right = 1\nborder_width_bottom = 1\nborder_color = Color(1, 1, 1, 0.207843)\ncorner_radius_top_left = 5\ncorner_radius_top_right = 5\ncorner_radius_bottom_right = 5\ncorner_radius_bottom_left = 5\n\n[sub_resource type=\"StyleBoxEmpty\" id=\"StyleBoxEmpty_2pbsy\"]\n\n[sub_resource type=\"LabelSettings\" id=\"LabelSettings_3jkpq\"]\nfont_size = 18\n\n[sub_resource type=\"StyleBoxFlat\" id=\"StyleBoxFlat_0awfk\"]\nbg_color = Color(0, 0, 0, 0.14902)\nborder_width_left = 1\nborder_width_top = 1\nborder_width_right = 1\nborder_width_bottom = 1\nborder_color = Color(0.8, 0.8, 0.8, 0.145098)\ncorner_radius_top_left = 5\ncorner_radius_top_right = 5\ncorner_radius_bottom_right = 5\ncorner_radius_bottom_left = 5\n\n[sub_resource type=\"LabelSettings\" id=\"LabelSettings_d4isr\"]\n\n[sub_resource type=\"DPITexture\" id=\"DPITexture_potss\"]\n_source = \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"16\\\" height=\\\"16\\\"><path fill=\\\"#e0e0e0\\\" d=\\\"M2 1a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h1V3h9V2a1 1 0 0 0-1-1zm3 3a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1zm1 2h7v7H6z\\\"/></svg>\n\"\nbase_scale = 1.25\ncolor_map = {\nColor(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),\nColor(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),\nColor(1, 0.8666667, 0.39607844, 1): Color(1, 0.87, 0.4, 1)\n}\n\n[sub_resource type=\"DPITexture\" id=\"DPITexture_piloo\"]\n_source = \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"16\\\" height=\\\"16\\\"><path fill=\\\"#e0e0e0\\\" d=\\\"M4 12a1 1 0 0 0 1.555.832l6-4a1 1 0 0 0 0-1.664l-6-4A1 1 0 0 0 4 4z\\\"/></svg>\n\"\nbase_scale = 1.25\ncolor_map = {\nColor(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),\nColor(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),\nColor(1, 0.8666667, 0.39607844, 1): Color(1, 0.87, 0.4, 1)\n}\n\n[sub_resource type=\"DPITexture\" id=\"DPITexture_02ixt\"]\n_source = \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"16\\\" height=\\\"16\\\"><rect width=\\\"10\\\" height=\\\"10\\\" x=\\\"3\\\" y=\\\"3\\\" fill=\\\"#e0e0e0\\\" rx=\\\"1\\\"/></svg>\n\"\nbase_scale = 1.25\ncolor_map = {\nColor(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),\nColor(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),\nColor(1, 0.8666667, 0.39607844, 1): Color(1, 0.87, 0.4, 1)\n}\n\n[sub_resource type=\"InputEventKey\" id=\"InputEventKey_w47tf\"]\ndevice = -1\nkeycode = 4194305\n\n[sub_resource type=\"Shortcut\" id=\"Shortcut_rarey\"]\nevents = [SubResource(\"InputEventKey_w47tf\")]\n\n[node name=\"FmodBankExplorer\" type=\"Window\"]\noversampling_override = 1.0\ntitle = \"Fmod banks explorer\"\ninitial_position = 2\nsize = Vector2i(1280, 673)\nscript = ExtResource(\"1_ekqus\")\n\n[node name=\"BGPanel\" type=\"Panel\" parent=\".\"]\nunique_name_in_owner = true\nanchors_preset = 15\nanchor_right = 1.0\nanchor_bottom = 1.0\ngrow_horizontal = 2\ngrow_vertical = 2\n\n[node name=\"WindowMargin\" type=\"MarginContainer\" parent=\".\"]\nanchors_preset = 15\nanchor_right = 1.0\nanchor_bottom = 1.0\ngrow_horizontal = 2\ngrow_vertical = 2\ntheme = SubResource(\"Theme_02ixt\")\ntheme_override_constants/margin_left = 16\ntheme_override_constants/margin_top = 16\ntheme_override_constants/margin_right = 16\ntheme_override_constants/margin_bottom = 16\n\n[node name=\"VBoxContainer\" type=\"VBoxContainer\" parent=\"WindowMargin\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n\n[node name=\"TopPanel\" type=\"PanelContainer\" parent=\"WindowMargin/VBoxContainer\"]\ncustom_minimum_size = Vector2(0, 42.315)\nlayout_mode = 2\n\n[node name=\"HBoxContainer\" type=\"HBoxContainer\" parent=\"WindowMargin/VBoxContainer/TopPanel\"]\nlayout_mode = 2\nalignment = 2\n\n[node name=\"SearchField\" type=\"LineEdit\" parent=\"WindowMargin/VBoxContainer/TopPanel/HBoxContainer\"]\nunique_name_in_owner = true\ncustom_minimum_size = Vector2(200, 0)\nlayout_mode = 2\nsize_flags_horizontal = 2\nplaceholder_text = \"Search..\"\ncontext_menu_enabled = false\n\n[node name=\"RefreshBanksButton\" type=\"Button\" parent=\"WindowMargin/VBoxContainer/TopPanel/HBoxContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\ntext = \"Refresh Banks\"\n\n[node name=\"BaseColorPanel\" type=\"PanelContainer\" parent=\"WindowMargin/VBoxContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_vertical = 3\ntheme_override_styles/panel = SubResource(\"StyleBoxFlat_wrr0m\")\n\n[node name=\"MarginContainer\" type=\"MarginContainer\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel\"]\nlayout_mode = 2\nsize_flags_vertical = 3\ntheme_override_constants/margin_left = 8\ntheme_override_constants/margin_top = 8\ntheme_override_constants/margin_right = 8\ntheme_override_constants/margin_bottom = 8\n\n[node name=\"HSplitContainer\" type=\"HSplitContainer\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer\"]\nlayout_mode = 2\n\n[node name=\"Tree\" type=\"Tree\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\nsize_flags_stretch_ratio = 0.54\ntheme_override_styles/panel = SubResource(\"StyleBoxEmpty_2pbsy\")\nhide_root = true\n\n[node name=\"MarginContainer\" type=\"MarginContainer\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\ntheme_override_constants/margin_left = 8\ntheme_override_constants/margin_top = 8\ntheme_override_constants/margin_right = 8\ntheme_override_constants/margin_bottom = 8\n\n[node name=\"RightPanelContent\" type=\"VBoxContainer\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n\n[node name=\"PathsLabel\" type=\"Label\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent\"]\nvisible = false\nlayout_mode = 2\nsize_flags_vertical = 1\ntext = \"ID:\"\nlabel_settings = SubResource(\"LabelSettings_3jkpq\")\njustification_flags = 35\n\n[node name=\"PathsBG\" type=\"PanelContainer\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent\"]\nunique_name_in_owner = true\nvisible = false\nlayout_mode = 2\nsize_flags_horizontal = 3\ntheme_override_styles/panel = SubResource(\"StyleBoxFlat_0awfk\")\n\n[node name=\"MarginContainer\" type=\"MarginContainer\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/PathsBG\"]\nlayout_mode = 2\ntheme_override_constants/margin_left = 10\ntheme_override_constants/margin_top = 10\ntheme_override_constants/margin_right = 10\ntheme_override_constants/margin_bottom = 10\n\n[node name=\"PathContainer\" type=\"HBoxContainer\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/PathsBG/MarginContainer\"]\nlayout_mode = 2\n\n[node name=\"TitleLabelContainer\" type=\"VBoxContainer\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/PathsBG/MarginContainer/PathContainer\"]\nlayout_mode = 2\n\n[node name=\"PathTitleLabel\" type=\"Label\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/PathsBG/MarginContainer/PathContainer/TitleLabelContainer\"]\nlayout_mode = 2\nsize_flags_vertical = 6\ntext = \"Path:\"\nlabel_settings = SubResource(\"LabelSettings_d4isr\")\n\n[node name=\"GuidTitleLabel\" type=\"Label\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/PathsBG/MarginContainer/PathContainer/TitleLabelContainer\"]\nlayout_mode = 2\nsize_flags_vertical = 6\ntext = \"GUID: \"\nlabel_settings = SubResource(\"LabelSettings_d4isr\")\n\n[node name=\"ValueContainer\" type=\"VBoxContainer\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/PathsBG/MarginContainer/PathContainer\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"PathLabel\" type=\"Label\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/PathsBG/MarginContainer/PathContainer/ValueContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_horizontal = 0\nsize_flags_vertical = 6\ntext = \"asdfasdfasdfasdf\"\n\n[node name=\"CopyPathLabel\" type=\"Button\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/PathsBG/MarginContainer/PathContainer/ValueContainer/PathLabel\"]\nvisible = false\nlayout_mode = 1\nanchors_preset = 6\nanchor_left = 1.0\nanchor_top = 0.5\nanchor_right = 1.0\nanchor_bottom = 0.5\noffset_left = 5.57001\noffset_top = -15.5\noffset_right = 36.57\noffset_bottom = 15.5\ngrow_horizontal = 0\ngrow_vertical = 2\nicon = SubResource(\"DPITexture_potss\")\n\n[node name=\"GuidLabel\" type=\"Label\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/PathsBG/MarginContainer/PathContainer/ValueContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_horizontal = 0\nsize_flags_vertical = 6\ntext = \"asdfasdf\"\nvertical_alignment = 1\n\n[node name=\"CopyGuidLabel\" type=\"Button\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/PathsBG/MarginContainer/PathContainer/ValueContainer/GuidLabel\"]\nvisible = false\nlayout_mode = 1\nanchors_preset = 6\nanchor_left = 1.0\nanchor_top = 0.5\nanchor_right = 1.0\nanchor_bottom = 0.5\noffset_left = 6.095\noffset_top = -15.5\noffset_right = 37.095\noffset_bottom = 15.5\ngrow_horizontal = 0\ngrow_vertical = 2\nicon = SubResource(\"DPITexture_potss\")\n\n[node name=\"ParametersLabel\" type=\"Label\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent\"]\nunique_name_in_owner = true\nvisible = false\ncustom_minimum_size = Vector2(0, 45)\nlayout_mode = 2\ntext = \"Parameters:\"\nlabel_settings = SubResource(\"LabelSettings_3jkpq\")\nvertical_alignment = 2\n\n[node name=\"ParametersContainer\" type=\"PanelContainer\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent\"]\nunique_name_in_owner = true\nvisible = false\nlayout_mode = 2\nsize_flags_vertical = 3\ntheme_override_styles/panel = SubResource(\"StyleBoxFlat_0awfk\")\n\n[node name=\"MarginContainer\" type=\"MarginContainer\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/ParametersContainer\"]\nlayout_mode = 2\ntheme_override_constants/margin_left = 10\ntheme_override_constants/margin_top = 10\ntheme_override_constants/margin_right = 10\ntheme_override_constants/margin_bottom = 10\n\n[node name=\"EventParametersDisplay\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/ParametersContainer/MarginContainer\" instance=ExtResource(\"2_uoyg8\")]\nunique_name_in_owner = true\nlayout_mode = 2\n\n[node name=\"EventPlayControls\" type=\"PanelContainer\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent\" node_paths=PackedStringArray(\"play_button\", \"stop_button\", \"fade_out_toggle\")]\nunique_name_in_owner = true\nvisible = false\ncustom_minimum_size = Vector2(0, 55.44)\nlayout_mode = 2\nsize_flags_vertical = 10\nsize_flags_stretch_ratio = 0.1\ntheme_override_styles/panel = SubResource(\"StyleBoxFlat_0awfk\")\nscript = ExtResource(\"2_mleop\")\nplay_button = NodePath(\"MarginContainer/HBoxContainer/PlayEventButton\")\nstop_button = NodePath(\"MarginContainer/HBoxContainer/StopEventButton\")\nfade_out_toggle = NodePath(\"MarginContainer/HBoxContainer/FadeoutToggle\")\n\n[node name=\"MarginContainer\" type=\"MarginContainer\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/EventPlayControls\"]\nlayout_mode = 2\ntheme_override_constants/margin_left = 10\ntheme_override_constants/margin_top = 10\ntheme_override_constants/margin_right = 10\ntheme_override_constants/margin_bottom = 10\n\n[node name=\"HBoxContainer\" type=\"HBoxContainer\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/EventPlayControls/MarginContainer\"]\nlayout_mode = 2\n\n[node name=\"PlayEventButton\" type=\"Button\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/EventPlayControls/MarginContainer/HBoxContainer\"]\nlayout_mode = 2\ntext = \"Play\"\nicon = SubResource(\"DPITexture_piloo\")\n\n[node name=\"StopEventButton\" type=\"Button\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/EventPlayControls/MarginContainer/HBoxContainer\"]\nlayout_mode = 2\ntext = \"Stop\"\nicon = SubResource(\"DPITexture_02ixt\")\n\n[node name=\"FadeoutToggle\" type=\"CheckButton\" parent=\"WindowMargin/VBoxContainer/BaseColorPanel/MarginContainer/HSplitContainer/MarginContainer/RightPanelContent/EventPlayControls/MarginContainer/HBoxContainer\"]\nlayout_mode = 2\ntext = \"Allow fade out\"\n\n[node name=\"MarginContainer\" type=\"MarginContainer\" parent=\"WindowMargin/VBoxContainer\"]\nlayout_mode = 2\ntheme_override_constants/margin_top = 8\n\n[node name=\"HBoxContainer\" type=\"HBoxContainer\" parent=\"WindowMargin/VBoxContainer/MarginContainer\"]\nlayout_mode = 2\nalignment = 1\n\n[node name=\"MarginContainer2\" type=\"MarginContainer\" parent=\"WindowMargin/VBoxContainer/MarginContainer/HBoxContainer\"]\nlayout_mode = 2\ntheme_override_constants/margin_left = 8\ntheme_override_constants/margin_right = 8\n\n[node name=\"SelectButton\" type=\"Button\" parent=\"WindowMargin/VBoxContainer/MarginContainer/HBoxContainer/MarginContainer2\"]\nunique_name_in_owner = true\nvisible = false\nlayout_mode = 2\nsize_flags_horizontal = 4\ntext = \"Select\"\n\n[node name=\"MarginContainer\" type=\"MarginContainer\" parent=\"WindowMargin/VBoxContainer/MarginContainer/HBoxContainer\"]\nlayout_mode = 2\ntheme_override_constants/margin_left = 8\ntheme_override_constants/margin_right = 8\n\n[node name=\"CloseButton\" type=\"Button\" parent=\"WindowMargin/VBoxContainer/MarginContainer/HBoxContainer/MarginContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_horizontal = 4\nshortcut = SubResource(\"Shortcut_rarey\")\ntext = \"Close\"\n\n[connection signal=\"text_submitted\" from=\"WindowMargin/VBoxContainer/TopPanel/HBoxContainer/SearchField\" to=\".\" method=\"_on_text_edit_text_submitted\"]\n"
  },
  {
    "path": "demo/addons/fmod/tool/ui/ParameterDisplay.gd",
    "content": "@tool class_name ParameterDisplay extends MarginContainer\n\nvar event_description: FmodEventDescription\nvar parameter: FmodParameterDescription\n\nfunc set_event_description(p_event_description: FmodEventDescription):\n\tevent_description = p_event_description\n\nfunc set_parameter(p_parameter: FmodParameterDescription):\n\tshow()\n\tparameter = p_parameter\n\nfunc display_value_selector(should: bool):\n\t%ValueSetterContainer.visible = should\n\nfunc _ready():\n\tif parameter == null:\n\t\thide()\n\t\treturn\n\tvar minimum_value = parameter.get_minimum()\n\tvar maximum_value = parameter.get_maximum()\n\tvar default_value = parameter.get_default_value()\n\t\n\tvar copy_icon : Texture = EditorInterface.get_editor_theme().get_icon(\"ActionCopy\", \"EditorIcons\")\n\t%NameCopyButton.icon = copy_icon\n\t%IdCopyButton.icon = copy_icon\n\t\n\t\n\t%NameLabel.text = parameter.get_name()\n\t%IdLabel.text = str(parameter.get_id())\n\tif parameter.is_labeled():\n\t\t%RangeTitle.text = \"Values\"\n\t\tvar values_text = \"[\"\n\t\tvar is_first: bool = true\n\t\tfor label: String in event_description.get_parameter_labels_by_id(parameter.get_id()):\n\t\t\tif not is_first:\n\t\t\t\tvalues_text += \", \"\n\t\t\tvalues_text += label\n\t\t\tis_first = false\n\t\tvalues_text += \"]\"\n\t\t%RangeLabel.text = values_text\n\telse:\n\t\t%RangeLabel.text = \"[%s, %s]\" % [minimum_value, maximum_value]\n\t%DefaultValueLabel.text = str(default_value)\n\t%NameCopyButton.pressed.connect(_on_copy_name_button)\n\t%IdCopyButton.pressed.connect(_on_copy_id_button)\n\t%BackToDefaultButton.pressed.connect(_on_default_value_button)\n\t\n\t%ValueSlider.min_value = minimum_value\n\t%ValueSlider.max_value = maximum_value\n\t%ValueSlider.value = default_value\n\t\n\t_on_slider_value_changed(%ValueSlider.value)\n\t%ValueSlider.value_changed.connect(_on_slider_value_changed)\n\nfunc _on_copy_name_button():\n\tDisplayServer.clipboard_set(%NameLabel.text)\n\nfunc _on_copy_id_button():\n\tDisplayServer.clipboard_set(%IdLabel.text)\n\nfunc _on_default_value_button():\n\t%ValueSlider.value = parameter.get_default_value()\n\nfunc _on_slider_value_changed(value: float):\n\t%CurrentValueLabel.text = str(value)\n"
  },
  {
    "path": "demo/addons/fmod/tool/ui/ParameterDisplay.gd.uid",
    "content": "uid://ve6g43nb1hdd\n"
  },
  {
    "path": "demo/addons/fmod/tool/ui/ParameterDisplay.tscn",
    "content": "[gd_scene load_steps=2 format=3 uid=\"uid://bfdldojk5i6u3\"]\n\n[ext_resource type=\"Script\" uid=\"uid://ve6g43nb1hdd\" path=\"res://addons/fmod/tool/ui/ParameterDisplay.gd\" id=\"1_fxyw8\"]\n\n[node name=\"ParameterDisplay\" type=\"MarginContainer\"]\nvisible = false\noffset_right = 168.0\noffset_bottom = 160.0\nsize_flags_horizontal = 3\ntheme_override_constants/margin_left = 4\ntheme_override_constants/margin_top = 4\ntheme_override_constants/margin_right = 4\ntheme_override_constants/margin_bottom = 4\nscript = ExtResource(\"1_fxyw8\")\n\n[node name=\"VBoxContainer\" type=\"VBoxContainer\" parent=\".\"]\nlayout_mode = 2\n\n[node name=\"VBoxContainer\" type=\"HBoxContainer\" parent=\"VBoxContainer\"]\nlayout_mode = 2\n\n[node name=\"TitleContainer\" type=\"VBoxContainer\" parent=\"VBoxContainer/VBoxContainer\"]\nlayout_mode = 2\ntheme_override_constants/separation = 20\n\n[node name=\"NameTitle\" type=\"Label\" parent=\"VBoxContainer/VBoxContainer/TitleContainer\"]\nlayout_mode = 2\nsize_flags_vertical = 10\ntext = \"Name: \"\n\n[node name=\"IdTitle\" type=\"Label\" parent=\"VBoxContainer/VBoxContainer/TitleContainer\"]\nlayout_mode = 2\nsize_flags_vertical = 10\ntext = \"ID: \"\n\n[node name=\"RangeTitle\" type=\"Label\" parent=\"VBoxContainer/VBoxContainer/TitleContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_vertical = 10\ntext = \"Range: \"\n\n[node name=\"DefaultValueTitle\" type=\"Label\" parent=\"VBoxContainer/VBoxContainer/TitleContainer\"]\nlayout_mode = 2\nsize_flags_vertical = 10\ntext = \"Default value: \"\n\n[node name=\"ContentContainer\" type=\"VBoxContainer\" parent=\"VBoxContainer/VBoxContainer\"]\nlayout_mode = 2\ntheme_override_constants/separation = 20\n\n[node name=\"NameLabel\" type=\"Label\" parent=\"VBoxContainer/VBoxContainer/ContentContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_horizontal = 0\nsize_flags_vertical = 10\n\n[node name=\"NameCopyButton\" type=\"Button\" parent=\"VBoxContainer/VBoxContainer/ContentContainer/NameLabel\"]\nunique_name_in_owner = true\nlayout_mode = 1\nanchors_preset = 6\nanchor_left = 1.0\nanchor_top = 0.5\nanchor_right = 1.0\nanchor_bottom = 0.5\noffset_left = 9.0\noffset_top = -15.5\noffset_right = 40.0\noffset_bottom = 15.5\ngrow_horizontal = 0\ngrow_vertical = 2\nsize_flags_vertical = 10\n\n[node name=\"IdLabel\" type=\"Label\" parent=\"VBoxContainer/VBoxContainer/ContentContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_horizontal = 0\nsize_flags_vertical = 10\n\n[node name=\"IdCopyButton\" type=\"Button\" parent=\"VBoxContainer/VBoxContainer/ContentContainer/IdLabel\"]\nunique_name_in_owner = true\nlayout_mode = 1\nanchors_preset = 6\nanchor_left = 1.0\nanchor_top = 0.5\nanchor_right = 1.0\nanchor_bottom = 0.5\noffset_left = 9.0\noffset_top = -15.5\noffset_right = 40.0\noffset_bottom = 15.5\ngrow_horizontal = 0\ngrow_vertical = 2\nsize_flags_vertical = 10\n\n[node name=\"RangeLabel\" type=\"Label\" parent=\"VBoxContainer/VBoxContainer/ContentContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_horizontal = 0\nsize_flags_vertical = 10\n\n[node name=\"DefaultValueLabel\" type=\"Label\" parent=\"VBoxContainer/VBoxContainer/ContentContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_horizontal = 0\nsize_flags_vertical = 10\n\n[node name=\"ValueSetterContainer\" type=\"VBoxContainer\" parent=\"VBoxContainer\"]\nunique_name_in_owner = true\nvisible = false\nlayout_mode = 2\n\n[node name=\"HSeparator\" type=\"HSeparator\" parent=\"VBoxContainer/ValueSetterContainer\"]\nlayout_mode = 2\n\n[node name=\"HBoxContainer\" type=\"HBoxContainer\" parent=\"VBoxContainer/ValueSetterContainer\"]\nlayout_mode = 2\n\n[node name=\"Label\" type=\"Label\" parent=\"VBoxContainer/ValueSetterContainer/HBoxContainer\"]\nlayout_mode = 2\ntext = \"Set value: \"\n\n[node name=\"ValueSlider\" type=\"HSlider\" parent=\"VBoxContainer/ValueSetterContainer/HBoxContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 4\n\n[node name=\"BackToDefaultButton\" type=\"Button\" parent=\"VBoxContainer/ValueSetterContainer/HBoxContainer\"]\nunique_name_in_owner = true\nlayout_mode = 2\ntext = \"Default\"\n\n[node name=\"HBoxContainer2\" type=\"HBoxContainer\" parent=\"VBoxContainer/ValueSetterContainer\"]\nlayout_mode = 2\n\n[node name=\"CurrentValueTitleLabel\" type=\"Label\" parent=\"VBoxContainer/ValueSetterContainer/HBoxContainer2\"]\nlayout_mode = 2\ntext = \"Current value: \"\n\n[node name=\"CurrentValueLabel\" type=\"Label\" parent=\"VBoxContainer/ValueSetterContainer/HBoxContainer2\"]\nunique_name_in_owner = true\nlayout_mode = 2\n\n[node name=\"Button\" type=\"Button\" parent=\"VBoxContainer/ValueSetterContainer\"]\nlayout_mode = 2\ntext = \"Select\"\n"
  },
  {
    "path": "demo/addons/fmod/tool/ui/TestFmodBankExplorer.tscn",
    "content": "[gd_scene load_steps=2 format=3 uid=\"uid://f4i35731qm63\"]\n\n[ext_resource type=\"PackedScene\" uid=\"uid://nr38urn226al\" path=\"res://addons/fmod/tool/ui/FmodBankExplorer.tscn\" id=\"1_0ul6h\"]\n\n[node name=\"TestFmodBankExplorer\" type=\"Node2D\"]\n\n[node name=\"FmodBankLoader\" type=\"FmodBankLoader\" parent=\".\"]\nbank_paths = [\"res://assets/Banks/Master.strings.bank\", \"res://assets/Banks/Master.bank\", \"res://assets/Banks/Music.bank\", \"res://assets/Banks/Vehicles.bank\"]\n\n[node name=\"FmodBankExplorer\" parent=\"FmodBankLoader\" instance=ExtResource(\"1_0ul6h\")]\ninitial_position = 0\nposition = Vector2i(0, 36)\n"
  },
  {
    "path": "demo/addons/gut/GutScene.gd",
    "content": "extends Node2D\n# ##############################################################################\n# This is a wrapper around the normal and compact gui controls and serves as\n# the interface between gut.gd and the gui.  The GutRunner creates an instance\n# of this and then this takes care of managing the different GUI controls.\n# ##############################################################################\n@onready var _normal_gui = $Normal\n@onready var _compact_gui = $Compact\n\nvar gut = null :\n\tset(val):\n\t\tgut = val\n\t\t_set_gut(val)\n\n\nfunc _ready():\n\t_normal_gui.switch_modes.connect(use_compact_mode.bind(true))\n\t_compact_gui.switch_modes.connect(use_compact_mode.bind(false))\n\n\t_normal_gui.set_title(\"GUT\")\n\t_compact_gui.set_title(\"GUT\")\n\n\t_normal_gui.align_right()\n\t_compact_gui.to_bottom_right()\n\n\tuse_compact_mode(false)\n\n\tif(get_parent() == get_tree().root):\n\t\t_test_running_setup()\n\nfunc _test_running_setup():\n\tset_font_size(100)\n\t_normal_gui.get_textbox().text = \"hello world, how are you doing?\"\n\n# ------------------------\n# Private\n# ------------------------\nfunc _set_gut(val):\n\tif(_normal_gui.get_gut() == val):\n\t\treturn\n\t_normal_gui.set_gut(val)\n\t_compact_gui.set_gut(val)\n\n\tval.start_run.connect(_on_gut_start_run)\n\tval.end_run.connect(_on_gut_end_run)\n\tval.start_pause_before_teardown.connect(_on_gut_pause)\n\tval.end_pause_before_teardown.connect(_on_pause_end)\n\nfunc _set_both_titles(text):\n\t_normal_gui.set_title(text)\n\t_compact_gui.set_title(text)\n\n\n# ------------------------\n# Events\n# ------------------------\nfunc _on_gut_start_run():\n\t_set_both_titles('Running')\n\nfunc _on_gut_end_run():\n\t_set_both_titles('Finished')\n\nfunc _on_gut_pause():\n\t_set_both_titles('-- Paused --')\n\nfunc _on_pause_end():\n\t_set_both_titles('Running')\n\n\n# ------------------------\n# Public\n# ------------------------\nfunc get_textbox():\n\treturn _normal_gui.get_textbox()\n\n\nfunc set_font_size(new_size):\n\tvar rtl = _normal_gui.get_textbox()\n\n\trtl.set('theme_override_font_sizes/bold_italics_font_size', new_size)\n\trtl.set('theme_override_font_sizes/bold_font_size', new_size)\n\trtl.set('theme_override_font_sizes/italics_font_size', new_size)\n\trtl.set('theme_override_font_sizes/normal_font_size', new_size)\n\n\nfunc set_font(font_name):\n\t_set_all_fonts_in_rtl(_normal_gui.get_textbox(), font_name)\n\n\nfunc _set_font(rtl, font_name, custom_name):\n\tif(font_name == null):\n\t\trtl.remove_theme_font_override(custom_name)\n\telse:\n\t\tvar font_path = 'res://addons/gut/fonts/' + font_name + '.ttf'\n\t\tif(FileAccess.file_exists(font_path)):\n\t\t\tvar dyn_font = FontFile.new()\n\t\t\tdyn_font.load_dynamic_font('res://addons/gut/fonts/' + font_name + '.ttf')\n\t\t\trtl.add_theme_font_override(custom_name, dyn_font)\n\n\nfunc _set_all_fonts_in_rtl(rtl, base_name):\n\tif(base_name == 'Default'):\n\t\t_set_font(rtl, null, 'normal_font')\n\t\t_set_font(rtl, null, 'bold_font')\n\t\t_set_font(rtl, null, 'italics_font')\n\t\t_set_font(rtl, null, 'bold_italics_font')\n\telse:\n\t\t_set_font(rtl, base_name + '-Regular', 'normal_font')\n\t\t_set_font(rtl, base_name + '-Bold', 'bold_font')\n\t\t_set_font(rtl, base_name + '-Italic', 'italics_font')\n\t\t_set_font(rtl, base_name + '-BoldItalic', 'bold_italics_font')\n\n\nfunc set_default_font_color(color):\n\t_normal_gui.get_textbox().set('custom_colors/default_color', color)\n\n\nfunc set_background_color(color):\n\t_normal_gui.set_bg_color(color)\n\n\nfunc use_compact_mode(should=true):\n\t_compact_gui.visible = should\n\t_normal_gui.visible = !should\n\n\nfunc set_opacity(val):\n\t_normal_gui.modulate.a = val\n\t_compact_gui.modulate.a = val\n\nfunc set_title(text):\n\t_set_both_titles(text)\n"
  },
  {
    "path": "demo/addons/gut/GutScene.gd.uid",
    "content": "uid://bw7tukh738kw1\n"
  },
  {
    "path": "demo/addons/gut/GutScene.tscn",
    "content": "[gd_scene load_steps=4 format=3 uid=\"uid://m28heqtswbuq\"]\n\n[ext_resource type=\"Script\" uid=\"uid://bw7tukh738kw1\" path=\"res://addons/gut/GutScene.gd\" id=\"1_b4m8y\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://duxblir3vu8x7\" path=\"res://addons/gut/gui/NormalGui.tscn\" id=\"2_j6ywb\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://cnqqdfsn80ise\" path=\"res://addons/gut/gui/MinGui.tscn\" id=\"3_3glw1\"]\n\n[node name=\"GutScene\" type=\"Node2D\"]\nscript = ExtResource(\"1_b4m8y\")\n\n[node name=\"Normal\" parent=\".\" instance=ExtResource(\"2_j6ywb\")]\n\n[node name=\"Compact\" parent=\".\" instance=ExtResource(\"3_3glw1\")]\noffset_left = 5.0\noffset_top = 273.0\noffset_right = 265.0\noffset_bottom = 403.0\n"
  },
  {
    "path": "demo/addons/gut/LICENSE.md",
    "content": "The MIT License (MIT)\n=====================\n\nCopyright (c) 2018 Tom \"Butch\" Wesley\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"
  },
  {
    "path": "demo/addons/gut/UserFileViewer.gd",
    "content": "extends Window\n\n@onready var rtl = $TextDisplay/RichTextLabel\n\nfunc _get_file_as_text(path):\n\tvar to_return = null\n\tvar f = FileAccess.open(path, FileAccess.READ)\n\tif(f != null):\n\t\tto_return = f.get_as_text()\n\telse:\n\t\tto_return = str('ERROR:  Could not open file.  Error code ', FileAccess.get_open_error())\n\treturn to_return\n\nfunc _ready():\n\trtl.clear()\n\nfunc _on_OpenFile_pressed():\n\t$FileDialog.popup_centered()\n\nfunc _on_FileDialog_file_selected(path):\n\tshow_file(path)\n\nfunc _on_Close_pressed():\n\tself.hide()\n\nfunc show_file(path):\n\tvar text = _get_file_as_text(path)\n\tif(text == ''):\n\t\ttext = '<Empty File>'\n\trtl.set_text(text)\n\tself.window_title = path\n\nfunc show_open():\n\tself.popup_centered()\n\t$FileDialog.popup_centered()\n\nfunc get_rich_text_label():\n\treturn $TextDisplay/RichTextLabel\n\nfunc _on_Home_pressed():\n\trtl.scroll_to_line(0)\n\nfunc _on_End_pressed():\n\trtl.scroll_to_line(rtl.get_line_count() -1)\n\nfunc _on_Copy_pressed():\n\treturn\n\t# OS.clipboard = rtl.text\n\nfunc _on_file_dialog_visibility_changed():\n\tif rtl.text.length() == 0 and not $FileDialog.visible:\n\t\tself.hide()\n"
  },
  {
    "path": "demo/addons/gut/UserFileViewer.gd.uid",
    "content": "uid://x51wilphva3d\n"
  },
  {
    "path": "demo/addons/gut/UserFileViewer.tscn",
    "content": "[gd_scene load_steps=2 format=3 uid=\"uid://bsm7wtt1gie4v\"]\n\n[ext_resource type=\"Script\" uid=\"uid://x51wilphva3d\" path=\"res://addons/gut/UserFileViewer.gd\" id=\"1\"]\n\n[node name=\"UserFileViewer\" type=\"Window\"]\nexclusive = true\nscript = ExtResource(\"1\")\n\n[node name=\"FileDialog\" type=\"FileDialog\" parent=\".\"]\naccess = 1\nshow_hidden_files = true\n__meta__ = {\n\"_edit_use_anchors_\": false\n}\n\n[node name=\"TextDisplay\" type=\"ColorRect\" parent=\".\"]\nanchor_right = 1.0\nanchor_bottom = 1.0\noffset_left = 8.0\noffset_right = -10.0\noffset_bottom = -65.0\ncolor = Color(0.2, 0.188235, 0.188235, 1)\n\n[node name=\"RichTextLabel\" type=\"RichTextLabel\" parent=\"TextDisplay\"]\nanchor_right = 1.0\nanchor_bottom = 1.0\nfocus_mode = 2\ntext = \"In publishing and graphic design, Lorem ipsum is a placeholder text commonly used to demonstrate the visual form of a document or a typeface without relying on meaningful content. Lorem ipsum may be used before final copy is available, but it may also be used to temporarily replace copy in a process called greeking, which allows designers to consider form without the meaning of the text influencing the design.\n\nLorem ipsum is typically a corrupted version of De finibus bonorum et malorum, a first-century BCE text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical, improper Latin.\n\nVersions of the Lorem ipsum text have been used in typesetting at least since the 1960s, when it was popularized by advertisements for Letraset transfer sheets. Lorem ipsum was introduced to the digital world in the mid-1980s when Aldus employed it in graphic and word-processing templates for its desktop publishing program PageMaker. Other popular word processors including Pages and Microsoft Word have since adopted Lorem ipsum as well.\"\nselection_enabled = true\n\n[node name=\"OpenFile\" type=\"Button\" parent=\".\"]\nanchor_left = 1.0\nanchor_top = 1.0\nanchor_right = 1.0\nanchor_bottom = 1.0\noffset_left = -158.0\noffset_top = -50.0\noffset_right = -84.0\noffset_bottom = -30.0\ntext = \"Open File\"\n\n[node name=\"Home\" type=\"Button\" parent=\".\"]\nanchor_left = 1.0\nanchor_top = 1.0\nanchor_right = 1.0\nanchor_bottom = 1.0\noffset_left = -478.0\noffset_top = -50.0\noffset_right = -404.0\noffset_bottom = -30.0\ntext = \"Home\"\n\n[node name=\"Copy\" type=\"Button\" parent=\".\"]\nanchor_top = 1.0\nanchor_bottom = 1.0\noffset_left = 160.0\noffset_top = -50.0\noffset_right = 234.0\noffset_bottom = -30.0\ntext = \"Copy\"\n\n[node name=\"End\" type=\"Button\" parent=\".\"]\nanchor_left = 1.0\nanchor_top = 1.0\nanchor_right = 1.0\nanchor_bottom = 1.0\noffset_left = -318.0\noffset_top = -50.0\noffset_right = -244.0\noffset_bottom = -30.0\ntext = \"End\"\n\n[node name=\"Close\" type=\"Button\" parent=\".\"]\nanchor_top = 1.0\nanchor_bottom = 1.0\noffset_left = 10.0\noffset_top = -50.0\noffset_right = 80.0\noffset_bottom = -30.0\ntext = \"Close\"\n\n[connection signal=\"file_selected\" from=\"FileDialog\" to=\".\" method=\"_on_FileDialog_file_selected\"]\n[connection signal=\"visibility_changed\" from=\"FileDialog\" to=\".\" method=\"_on_file_dialog_visibility_changed\"]\n[connection signal=\"pressed\" from=\"OpenFile\" to=\".\" method=\"_on_OpenFile_pressed\"]\n[connection signal=\"pressed\" from=\"Home\" to=\".\" method=\"_on_Home_pressed\"]\n[connection signal=\"pressed\" from=\"Copy\" to=\".\" method=\"_on_Copy_pressed\"]\n[connection signal=\"pressed\" from=\"End\" to=\".\" method=\"_on_End_pressed\"]\n[connection signal=\"pressed\" from=\"Close\" to=\".\" method=\"_on_Close_pressed\"]\n"
  },
  {
    "path": "demo/addons/gut/autofree.gd",
    "content": "# ##############################################################################\n#(G)odot (U)nit (T)est class\n#\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n# Class used to keep track of objects to be freed and utilities to free them.\n# ##############################################################################\nvar _to_free = []\nvar _to_queue_free = []\nvar _ref_counted_doubles = []\nvar _all_instance_ids = []\n\n\nfunc _add_instance_id(thing):\n\tif(thing.has_method(\"get_instance_id\")):\n\t\t_all_instance_ids.append(thing.get_instance_id())\n\n\nfunc add_free(thing):\n\tif(typeof(thing) == TYPE_OBJECT):\n\t\t_add_instance_id(thing)\n\t\tif(!thing is RefCounted):\n\t\t\t_to_free.append(thing)\n\t\telif(GutUtils.is_double(thing)):\n\t\t\t_ref_counted_doubles.append(thing)\n\n\nfunc add_queue_free(thing):\n\tif(typeof(thing) == TYPE_OBJECT):\n\t\t_add_instance_id(thing)\n\t\t_to_queue_free.append(thing)\n\n\nfunc get_queue_free_count():\n\treturn _to_queue_free.size()\n\n\nfunc get_free_count():\n\treturn _to_free.size()\n\n\nfunc free_all():\n\tfor node in _to_free:\n\t\tif(is_instance_valid(node)):\n\t\t\tif(GutUtils.is_double(node)):\n\t\t\t\tnode.__gutdbl_done()\n\t\t\tnode.free()\n\t_to_free.clear()\n\n\tfor i in range(_to_queue_free.size()):\n\t\tif(is_instance_valid(_to_queue_free[i])):\n\t\t\t_to_queue_free[i].queue_free()\n\t_to_queue_free.clear()\n\n\tfor ref_dbl in _ref_counted_doubles:\n\t\tref_dbl.__gutdbl_done()\n\t_ref_counted_doubles.clear()\n\n\t_all_instance_ids.clear()\n\n\nfunc has_instance_id(id):\n\treturn _all_instance_ids.has(id)"
  },
  {
    "path": "demo/addons/gut/autofree.gd.uid",
    "content": "uid://bxjfriqxgwe0r\n"
  },
  {
    "path": "demo/addons/gut/awaiter.gd",
    "content": "extends Node\n\nclass AwaitLogger:\n\tvar _time_waited = 0.0\n\tvar logger = GutUtils.get_logger()\n\tvar waiting_on = \"nothing\"\n\tvar logged_initial_message = false\n\tvar wait_log_delay := 1.0\n\tvar disabled = false\n\n\tfunc waited(x):\n\t\t_time_waited += x\n\t\tif(!logged_initial_message and _time_waited >= wait_log_delay):\n\t\t\tlog_it()\n\t\t\tlogged_initial_message = true\n\n\n\tfunc reset():\n\t\t_time_waited = 0.0\n\t\tlogged_initial_message = false\n\n\n\tfunc log_it():\n\t\tif(!disabled):\n\t\t\tvar msg = str(\"--- Awaiting \", waiting_on, \" ---\")\n\t\t\tlogger.wait_msg(msg)\n\n\n\n\nsignal timeout\nsignal wait_started\n\nvar await_logger = AwaitLogger.new()\nvar _wait_time := 0.0\nvar _wait_process_frames := 0\nvar _wait_physics_frames := 0\nvar _signal_to_wait_on = null\n\nvar _predicate_method = null\nvar _waiting_for_predicate_to_be = null\n\nvar _predicate_time_between := 0.0\nvar _predicate_time_between_elpased := 0.0\n\nvar _elapsed_time := 0.0\nvar _elapsed_frames := 0\n\nvar _did_last_wait_timeout = false\nvar did_last_wait_timeout = false :\n\tget: return _did_last_wait_timeout\n\tset(val): push_error(\"Cannot set did_last_wait_timeout\")\n\n\n\nfunc _ready() -> void:\n\tget_tree().process_frame.connect(_on_tree_process_frame)\n\tget_tree().physics_frame.connect(_on_tree_physics_frame)\n\n\nfunc _on_tree_process_frame():\n\t# Count frames here instead of in _process so that tree order never\n\t# makes a difference and the count/signaling happens outside of\n\t# _process being called.\n\tif(_wait_process_frames > 0):\n\t\t_elapsed_frames += 1\n\t\tif(_elapsed_frames > _wait_process_frames):\n\t\t\t_end_wait()\n\n\nfunc _on_tree_physics_frame():\n\t# Count frames here instead of in _physics_process so that tree order never\n\t# makes a difference and the count/signaling happens outside of\n\t# _physics_process being called.\n\tif(_wait_physics_frames != 0):\n\t\t_elapsed_frames += 1\n\t\tif(_elapsed_frames > _wait_physics_frames):\n\t\t\t_end_wait()\n\n\nfunc _physics_process(delta):\n\tif(is_waiting()):\n\t\tawait_logger.waited(delta)\n\n\tif(_wait_time != 0.0):\n\t\t_elapsed_time += delta\n\t\tif(_elapsed_time >= _wait_time):\n\t\t\t_end_wait()\n\n\tif(_predicate_method != null):\n\t\t_predicate_time_between_elpased += delta\n\t\tif(_predicate_time_between_elpased >= _predicate_time_between):\n\t\t\t_predicate_time_between_elpased = 0.0\n\t\t\tvar result = _predicate_method.call()\n\t\t\tif(_waiting_for_predicate_to_be == false):\n\t\t\t\tif(typeof(result) != TYPE_BOOL or result != true):\n\t\t\t\t\t_end_wait()\n\t\t\telse:\n\t\t\t\tif(typeof(result) == TYPE_BOOL and result == _waiting_for_predicate_to_be):\n\t\t\t\t\t_end_wait()\n\n\nfunc _end_wait():\n\tawait_logger.reset()\n\t# Check for time before checking for frames so that the extra frames added\n\t# when waiting on a signal do not cause a false negative for timing out.\n\tif(_wait_time > 0):\n\t\t_did_last_wait_timeout = _elapsed_time >= _wait_time\n\telif(_wait_physics_frames > 0):\n\t\t_did_last_wait_timeout = _elapsed_frames >= _wait_physics_frames\n\telif(_wait_process_frames > 0):\n\t\t_did_last_wait_timeout = _elapsed_frames >= _wait_process_frames\n\n\tif(_signal_to_wait_on != null and \\\n\t   is_instance_valid(_signal_to_wait_on.get_object()) and \\\n\t   _signal_to_wait_on.is_connected(_signal_callback)):\n\t\t_signal_to_wait_on.disconnect(_signal_callback)\n\n\t_wait_process_frames = 0\n\t_wait_time = 0.0\n\t_wait_physics_frames = 0\n\t_signal_to_wait_on = null\n\t_predicate_method = null\n\t_elapsed_time = 0.0\n\t_elapsed_frames = 0\n\ttimeout.emit()\n\n\nconst ARG_NOT_SET = '_*_argument_*_is_*_not_set_*_'\nfunc _signal_callback(\n\t\t_arg1=ARG_NOT_SET, _arg2=ARG_NOT_SET, _arg3=ARG_NOT_SET,\n\t\t_arg4=ARG_NOT_SET, _arg5=ARG_NOT_SET, _arg6=ARG_NOT_SET,\n\t\t_arg7=ARG_NOT_SET, _arg8=ARG_NOT_SET, _arg9=ARG_NOT_SET):\n\n\t_signal_to_wait_on.disconnect(_signal_callback)\n\t# DO NOT _end_wait here.  For other parts of the test to get the signal that\n\t# was waited on, we have to wait for another frames.  For example, the\n\t# signal_watcher doesn't get the signal in time if we don't do this.\n\t_wait_process_frames = 1\n\n\nfunc wait_seconds(x, msg=''):\n\tawait_logger.waiting_on = str(x, \" seconds \", msg)\n\t_did_last_wait_timeout = false\n\t_wait_time = x\n\twait_started.emit()\n\n\nfunc wait_process_frames(x, msg=''):\n\tawait_logger.waiting_on = str(x, \" idle frames \", msg)\n\t_did_last_wait_timeout = false\n\t_wait_process_frames = x\n\twait_started.emit()\n\n\nfunc wait_physics_frames(x, msg=''):\n\tawait_logger.waiting_on = str(x, \" physics frames \", msg)\n\t_did_last_wait_timeout = false\n\t_wait_physics_frames = x\n\twait_started.emit()\n\n\nfunc wait_for_signal(the_signal : Signal, max_time, msg=''):\n\tawait_logger.waiting_on = str(\"signal \", the_signal.get_name(), \" or \", max_time, \"s \", msg)\n\t_did_last_wait_timeout = false\n\tthe_signal.connect(_signal_callback)\n\t_signal_to_wait_on = the_signal\n\t_wait_time = max_time\n\twait_started.emit()\n\n\nfunc wait_until(predicate_function: Callable, max_time, time_between_calls:=0.0, msg=''):\n\tawait_logger.waiting_on = str(\"callable to return TRUE or \", max_time, \"s.  \", msg)\n\t_predicate_time_between = time_between_calls\n\t_predicate_method = predicate_function\n\t_wait_time = max_time\n\n\t_waiting_for_predicate_to_be = true\n\t_predicate_time_between_elpased = 0.0\n\t_did_last_wait_timeout = false\n\n\twait_started.emit()\n\n\nfunc wait_while(predicate_function: Callable, max_time, time_between_calls:=0.0, msg=''):\n\tawait_logger.waiting_on = str(\"callable to return FALSE or \", max_time, \"s.  \", msg)\n\t_predicate_time_between = time_between_calls\n\t_predicate_method = predicate_function\n\t_wait_time = max_time\n\n\t_waiting_for_predicate_to_be = false\n\t_predicate_time_between_elpased = 0.0\n\t_did_last_wait_timeout = false\n\n\twait_started.emit()\n\n\nfunc is_waiting():\n\treturn _wait_time != 0.0 || \\\n\t\t_wait_physics_frames != 0 || \\\n\t\t_wait_process_frames != 0\n"
  },
  {
    "path": "demo/addons/gut/awaiter.gd.uid",
    "content": "uid://ccu4ww35edtdi\n"
  },
  {
    "path": "demo/addons/gut/cli/change_project_warnings.gd",
    "content": "extends SceneTree\n\nvar Optparse = load('res://addons/gut/cli/optparse.gd')\nvar WarningsManager = load(\"res://addons/gut/warnings_manager.gd\")\nconst WARN_VALUE_PRINT_POSITION = 36\n\nvar godot_default_warnings = {\n  \"assert_always_false\": 1,             \"assert_always_true\": 1,  \t\t\t\"confusable_identifier\": 1,\n  \"confusable_local_declaration\": 1,    \"confusable_local_usage\": 1,  \t\t\"constant_used_as_function\": 1,\n  \"deprecated_keyword\": 1,              \"empty_file\": 1,  \t\t\t\t\t\"enable\": true,\n  \"exclude_addons\": true, \t\t\t\t\"function_used_as_property\": 1,  \t\"get_node_default_without_onready\": 2,\n  \"incompatible_ternary\": 1,  \t\t\t\"inference_on_variant\": 2,  \t\t\"inferred_declaration\": 0,\n  \"int_as_enum_without_cast\": 1,  \t\t\"int_as_enum_without_match\": 1,  \t\"integer_division\": 1,\n  \"narrowing_conversion\": 1,  \t\t\t\"native_method_override\": 2,  \t\t\"onready_with_export\": 2,\n  \"property_used_as_function\": 1,  \t\t\"redundant_await\": 1,  \t\t\t\t\"redundant_static_unload\": 1,\n  \"renamed_in_godot_4_hint\": 1,  \t\t\"return_value_discarded\": 0,  \t\t\"shadowed_global_identifier\": 1,\n  \"shadowed_variable\": 1,  \t\t\t\t\"shadowed_variable_base_class\": 1,  \"standalone_expression\": 1,\n  \"standalone_ternary\": 1,  \t\t\t\"static_called_on_instance\": 1,  \t\"unassigned_variable\": 1,\n  \"unassigned_variable_op_assign\": 1,  \t\"unreachable_code\": 1,  \t\t\t\"unreachable_pattern\": 1,\n  \"unsafe_call_argument\": 0,  \t\t\t\"unsafe_cast\": 0,  \t\t\t\t\t\"unsafe_method_access\": 0,\n  \"unsafe_property_access\": 0,  \t\t\"unsafe_void_return\": 1,  \t\t\t\"untyped_declaration\": 0,\n  \"unused_local_constant\": 1,  \t\t\t\"unused_parameter\": 1,  \t\t\t\"unused_private_class_variable\": 1,\n  \"unused_signal\": 1,  \t\t\t\t\t\"unused_variable\": 1\n}\n\nvar gut_default_changes = {\n  \"exclude_addons\": false, \t\t\t\t\"redundant_await\": 0,\n}\n\nvar warning_settings = {}\n\nfunc _setup_warning_settings():\n\twarning_settings[\"godot_default\"] = godot_default_warnings\n\twarning_settings[\"current\"] = WarningsManager.create_warnings_dictionary_from_project_settings()\n\twarning_settings[\"all_warn\"] = WarningsManager.create_warn_all_warnings_dictionary()\n\n\tvar gut_default = godot_default_warnings.duplicate()\n\tgut_default.merge(gut_default_changes, true)\n\twarning_settings[\"gut_default\"] = gut_default\n\n\nfunc _warn_value_to_s(value):\n\tvar readable = str(value).capitalize()\n\tif(typeof(value) == TYPE_INT):\n\t\treadable = WarningsManager.WARNING_LOOKUP.get(value, str(readable, ' ???'))\n\t\treadable = readable.capitalize()\n\treturn readable\n\n\nfunc _human_readable(warnings):\n\tvar to_return = \"\"\n\tfor key in warnings:\n\t\tvar readable = _warn_value_to_s(warnings[key])\n\t\tto_return += str(key.capitalize().rpad(35, ' '), readable, \"\\n\")\n\treturn to_return\n\n\nfunc _dump_settings(which):\n\tif(warning_settings.has(which)):\n\t\tGutUtils.pretty_print(warning_settings[which])\n\telse:\n\t\tprint(\"UNKNOWN print option \", which)\n\n\nfunc _print_settings(which):\n\tif(warning_settings.has(which)):\n\t\tprint(_human_readable(warning_settings[which]))\n\telse:\n\t\tprint(\"UNKNOWN print option \", which)\n\n\nfunc _apply_settings(which):\n\tif(!warning_settings.has(which)):\n\t\tprint(\"UNKNOWN set option \", which)\n\t\treturn\n\n\tvar pre_settings = warning_settings[\"current\"]\n\tvar new_settings = warning_settings[which]\n\n\tif(new_settings == pre_settings):\n\t\tprint(\"-- Settings are the same, no changes were made --\")\n\t\treturn\n\n\tWarningsManager.apply_warnings_dictionary(new_settings)\n\tProjectSettings.save()\n\tprint(\"-- Project Warning Settings have been updated --\")\n\tprint(_diff_changes_text(pre_settings))\n\n\nfunc _diff_text(w1, w2, diff_col_pad=10):\n\tvar to_return = \"\"\n\tfor key in w1:\n\t\tvar v1_text = _warn_value_to_s(w1[key])\n\t\tvar v2_text = _warn_value_to_s(w2[key])\n\t\tvar diff_text = v1_text\n\t\tvar prefix = \"  \"\n\n\t\tif(v1_text != v2_text):\n\t\t\tvar diff_prefix = \" \"\n\t\t\tif(w1[key] > w2[key]):\n\t\t\t\tdiff_prefix = \"-\"\n\t\t\telse:\n\t\t\t\tdiff_prefix = \"+\"\n\t\t\tprefix = \"* \"\n\t\t\tdiff_text = str(v1_text.rpad(diff_col_pad, ' '), diff_prefix, v2_text)\n\n\t\tto_return += str(str(prefix, key.capitalize()).rpad(WARN_VALUE_PRINT_POSITION, ' '), diff_text, \"\\n\")\n\n\treturn to_return.rstrip(\"\\n\")\n\n\nfunc _diff_changes_text(pre_settings):\n\tvar orig_diff_text = _diff_text(\n\t\tpre_settings,\n\t\tWarningsManager.create_warnings_dictionary_from_project_settings(),\n\t\t0)\n\t# these next two lines are fragile and brute force...enjoy\n\tvar diff_text = orig_diff_text.replace(\"-\", \" -> \")\n\tdiff_text = diff_text.replace(\"+\", \" -> \")\n\n\tif(orig_diff_text == diff_text):\n\t\tdiff_text += \"\\n-- No changes were made --\"\n\telse:\n\t\tdiff_text += \"\\nChanges will not be visible in Godot until it is restarted.\\n\"\n\t\tdiff_text += \"Even if it asks you to reload...Maybe.  Probably.\"\n\n\treturn diff_text\n\n\n\nfunc _diff(name_1, name_2):\n\tif(warning_settings.has(name_1) and warning_settings.has(name_2)):\n\t\tvar c2_pad = name_1.length() + 2\n\t\tvar heading = str(\" \".repeat(WARN_VALUE_PRINT_POSITION), name_1.rpad(c2_pad, ' '), name_2, \"\\n\")\n\t\theading += str(\n\t\t\t\" \".repeat(WARN_VALUE_PRINT_POSITION),\n\t\t\t\"-\".repeat(name_1.length()).rpad(c2_pad, \" \"),\n\t\t\t\"-\".repeat(name_2.length()),\n\t\t\t\"\\n\")\n\n\t\tvar text = _diff_text(warning_settings[name_1], warning_settings[name_2], c2_pad)\n\n\t\tprint(heading)\n\t\tprint(text)\n\n\t\tvar diff_count = 0\n\t\tfor line in text.split(\"\\n\"):\n\t\t\tif(!line.begins_with(\"  \")):\n\t\t\t\tdiff_count += 1\n\n\t\tif(diff_count == 0):\n\t\t\tprint('-- [', name_1, \"] and [\", name_2, \"] are the same --\")\n\t\telse:\n\t\t\tprint('-- There are ', diff_count, ' differences between [', name_1, \"] and [\", name_2, \"] --\")\n\telse:\n\t\tprint(\"One or more unknown Warning Level Names:, [\", name_1, \"] [\", name_2, \"]\")\n\n\nfunc _set_settings(nvps):\n\tvar pre_settings = warning_settings[\"current\"]\n\tfor i in range(nvps.size()/2):\n\t\tvar s_name = nvps[i * 2]\n\t\tvar s_value = nvps[i * 2 + 1]\n\t\tif(godot_default_warnings.has(s_name)):\n\t\t\tvar t = typeof(godot_default_warnings[s_name])\n\t\t\tif(t == TYPE_INT):\n\t\t\t\ts_value = s_value.to_int()\n\t\t\telif(t == TYPE_BOOL):\n\t\t\t\ts_value = s_value.to_lower() == 'true'\n\n\t\t\tWarningsManager.set_project_setting_warning(s_name, s_value)\n\t\t\tProjectSettings.save()\n\tprint(_diff_changes_text(pre_settings))\n\n\n\nfunc _setup_options():\n\tvar opts = Optparse.new()\n\topts.banner = \"\"\"\n\tThis script prints info about or sets the warning settings for the project.\n\tEach action requires one or more Warning Level Names.\n\n\tWarning Level Names:\n\t    * current        The current settings for the project.\n\t    * godot_default  The default settings for Godot.\n\t    * gut_default    The warning settings that is used when developing GUT.\n\t    * all_warn       Everything set to warn.\n\t\"\"\".dedent()\n\n\topts.add('-h', false, 'Print this help')\n\topts.add('-set', [], \"Sets a single setting in the project settings and saves.\\n\" +\n\t\t\t\t\t\t \"Use -dump to see a list of setting names and values.\\n\" +\n\t\t\t\t\t\t \"Example: -set enabled,true -set unsafe_cast,2 -set unreachable_code,0\")\n\topts.add_heading(\" Actions (require Warning Level Name)\")\n\topts.add('-diff', [], \"Shows the difference between two Warning Level Names.\\n\" +\n\t\t\t\t\t\t  \"Example:  -diff current,all_warn\")\n\topts.add('-dump', 'none', \"Prints a dictionary of the warning values.\")\n\topts.add('-print', 'none', \"Print human readable warning values.\")\n\topts.add('-apply', 'none', \"Applys one of the Warning Level Names to the project settings.  You should restart after using this\")\n\n\treturn opts\n\nfunc _print_help(opts):\n\topts.print_help()\n\n\n\nfunc _init():\n\t# Testing might set this flag but it should never be disabled for this tool\n\t# or it cannot save project settings, but says it did.  Sneakily use the\n\t# private property to get around this property being read-only.  Don't\n\t# try this at home.\n\tWarningsManager._disabled = false\n\n\t_setup_warning_settings()\n\n\tvar opts = _setup_options()\n\topts.parse()\n\n\tif(opts.unused.size() != 0):\n\t\topts.print_help()\n\t\tprint(\"Unknown arguments \", opts.unused)\n\tif(opts.values.h):\n\t\topts.print_help()\n\telif(opts.values.print != 'none'):\n\t\t_print_settings(opts.values.print)\n\telif(opts.values.dump != 'none'):\n\t\t_dump_settings(opts.values.dump)\n\telif(opts.values.apply != 'none'):\n\t\t_apply_settings(opts.values.apply )\n\telif(opts.values.diff.size() == 2):\n\t\t_diff(opts.values.diff[0], opts.values.diff[1])\n\telif(opts.values.set.size() % 2 == 0):\n\t\t_set_settings(opts.values.set)\n\telse:\n\t\topts.print_help()\n\t\tprint(\"You didn't specify any options or too many or not the right size or something invalid.  I don't know what you want to do.\")\n\n\tquit()"
  },
  {
    "path": "demo/addons/gut/cli/change_project_warnings.gd.uid",
    "content": "uid://1pauyfnd1cre\n"
  },
  {
    "path": "demo/addons/gut/cli/gut_cli.gd",
    "content": "extends Node\n\nvar Optparse = load('res://addons/gut/cli/optparse.gd')\nvar Gut = load('res://addons/gut/gut.gd')\nvar GutRunner = load('res://addons/gut/gui/GutRunner.tscn')\n\n# ------------------------------------------------------------------------------\n# Helper class to resolve the various different places where an option can\n# be set.  Using the get_value method will enforce the order of precedence of:\n# \t1.  command line value\n#\t2.  config file value\n#\t3.  default value\n#\n# The idea is that you set the base_opts.  That will get you a copies of the\n# hash with null values for the other types of values.  Lower precedented hashes\n# will punch through null values of higher precedented hashes.\n# ------------------------------------------------------------------------------\nclass OptionResolver:\n\tvar base_opts = {}\n\tvar cmd_opts = {}\n\tvar config_opts = {}\n\n\n\tfunc get_value(key):\n\t\treturn _nvl(cmd_opts[key], _nvl(config_opts[key], base_opts[key]))\n\n\tfunc set_base_opts(opts):\n\t\tbase_opts = opts\n\t\tcmd_opts = _null_copy(opts)\n\t\tconfig_opts = _null_copy(opts)\n\n\t# creates a copy of a hash with all values null.\n\tfunc _null_copy(h):\n\t\tvar new_hash = {}\n\t\tfor key in h:\n\t\t\tnew_hash[key] = null\n\t\treturn new_hash\n\n\tfunc _nvl(a, b):\n\t\tif(a == null):\n\t\t\treturn b\n\t\telse:\n\t\t\treturn a\n\n\tfunc _string_it(h):\n\t\tvar to_return = ''\n\t\tfor key in h:\n\t\t\tto_return += str('(',key, ':', _nvl(h[key], 'NULL'), ')')\n\t\treturn to_return\n\n\tfunc to_s():\n\t\treturn str(\"base:\\n\", _string_it(base_opts), \"\\n\", \\\n\t\t\t\t\"config:\\n\", _string_it(config_opts), \"\\n\", \\\n\t\t\t\t\"cmd:\\n\", _string_it(cmd_opts), \"\\n\", \\\n\t\t\t\t\"resolved:\\n\", _string_it(get_resolved_values()))\n\n\tfunc get_resolved_values():\n\t\tvar to_return = {}\n\t\tfor key in base_opts:\n\t\t\tto_return[key] = get_value(key)\n\t\treturn to_return\n\n\tfunc to_s_verbose():\n\t\tvar to_return = ''\n\t\tvar resolved = get_resolved_values()\n\t\tfor key in base_opts:\n\t\t\tto_return += str(key, \"\\n\")\n\t\t\tto_return += str('  default: ', _nvl(base_opts[key], 'NULL'), \"\\n\")\n\t\t\tto_return += str('  config:  ', _nvl(config_opts[key], ' --'), \"\\n\")\n\t\t\tto_return += str('  cmd:     ', _nvl(cmd_opts[key], ' --'), \"\\n\")\n\t\t\tto_return += str('  final:   ', _nvl(resolved[key], 'NULL'), \"\\n\")\n\n\t\treturn to_return\n\n# ------------------------------------------------------------------------------\n# Here starts the actual script that uses the Options class to kick off Gut\n# and run your tests.\n# ------------------------------------------------------------------------------\nvar _gut_config = load('res://addons/gut/gut_config.gd').new()\n\n# array of command line options specified\nvar _final_opts = []\n\n\nfunc setup_options(options, font_names):\n\tvar opts = Optparse.new()\n\topts.banner =\\\n\"\"\"\nThe GUT CLI\n-----------\nThe default behavior for GUT is to load options from a res://.gutconfig.json if\nit exists.  Any options specified on the command line will take precedence over\noptions specified in the gutconfig file.  You can specify a different gutconfig\nfile with the -gconfig option.\n\nTo generate a .gutconfig.json file you can use -gprint_gutconfig_sample\nTo see the effective values of a CLI command and a gutconfig use -gpo\n\nValues for options can be supplied using:\n    option=value    # no space around \"=\"\n    option value    # a space between option and value w/o =\n\nOptions whose values are lists/arrays can be specified multiple times:\n\t-gdir=a,b\n\t-gdir c,d\n\t-gdir e\n\t# results in -gdir equaling [a, b, c, d, e]\n\nTo not use an empty value instead of a default value, specifiy the option with\nan immediate \"=\":\n\t-gconfig=\n\"\"\"\n\topts.add_heading(\"Test Config:\")\n\topts.add('-gdir', options.dirs, 'List of directories to search for test scripts in.')\n\topts.add('-ginclude_subdirs', false, 'Flag to include all subdirectories specified with -gdir.')\n\topts.add('-gtest', [], 'List of full paths to test scripts to run.')\n\topts.add('-gprefix', options.prefix, 'Prefix used to find tests when specifying -gdir.  Default \"[default]\".')\n\topts.add('-gsuffix', options.suffix, 'Test script suffix, including .gd extension.  Default \"[default]\".')\n\topts.add('-gconfig', 'res://.gutconfig.json', 'The config file to load options from.  The default is [default].  Use \"-gconfig=\" to not use a config file.')\n\topts.add('-gpre_run_script', '', 'pre-run hook script path')\n\topts.add('-gpost_run_script', '', 'post-run hook script path')\n\topts.add('-gerrors_do_not_cause_failure', false, 'When an internal GUT error occurs tests will fail.  With this option set, that does not happen.')\n\topts.add('-gdouble_strategy', 'SCRIPT_ONLY', 'Default strategy to use when doubling.  Valid values are [INCLUDE_NATIVE, SCRIPT_ONLY].  Default \"[default]\"')\n\n\topts.add_heading(\"Run Options:\")\n\topts.add('-gselect', '', 'All scripts that contain the specified string in their filename will be ran')\n\topts.add('-ginner_class', '', 'Only run inner classes that contain the specified string in their name.')\n\topts.add('-gunit_test_name', '', 'Any test that contains the specified text will be run, all others will be skipped.')\n\topts.add('-gexit', false, 'Exit after running tests.  If not specified you have to manually close the window.')\n\topts.add('-gexit_on_success', false, 'Only exit if zero tests fail.')\n\topts.add('-gignore_pause', false, 'Ignores any calls to pause_before_teardown.')\n\topts.add('-gno_error_tracking', false, 'Disable error tracking.')\n\topts.add('-gfailure_error_types', options.failure_error_types, 'Error types that will cause tests to fail if the are encountered during the execution of a test.  Default \"[default]\"')\n\n\topts.add_heading(\"Display Settings:\")\n\topts.add('-glog', options.log_level, 'Log level [0-3].  Default [default]')\n\topts.add('-ghide_orphans', false, 'Display orphan counts for tests and scripts.  Default [default].')\n\topts.add('-gmaximize', false, 'Maximizes test runner window to fit the viewport.')\n\topts.add('-gcompact_mode', false, 'The runner will be in compact mode.  This overrides -gmaximize.')\n\topts.add('-gopacity', options.opacity, 'Set opacity of test runner window. Use range 0 - 100. 0 = transparent, 100 = opaque.')\n\topts.add('-gdisable_colors', false, 'Disable command line colors.')\n\topts.add('-gfont_name', options.font_name, str('Valid values are:  ', font_names, '.  Default \"[default]\"'))\n\topts.add('-gfont_size', options.font_size, 'Font size, default \"[default]\"')\n\topts.add('-gbackground_color', options.background_color, 'Background color as an html color, default \"[default]\"')\n\topts.add('-gfont_color',options.font_color, 'Font color as an html color, default \"[default]\"')\n\topts.add('-gpaint_after', options.paint_after, 'Delay before GUT will add a 1 frame pause to paint the screen/GUI.  default [default]')\n\topts.add('-gwait_log_delay', options.wait_log_delay, 'Delay before GUT will print a message to indicate a test is awaiting one of the wait_* methods.  Default [default]')\n\n\topts.add_heading(\"Result Export:\")\n\topts.add('-gjunit_xml_file', options.junit_xml_file, 'Export results of run to this file in the Junit XML format.')\n\topts.add('-gjunit_xml_timestamp', options.junit_xml_timestamp, 'Include a timestamp in the -gjunit_xml_file, default [default]')\n\n\topts.add_heading(\"Help:\")\n\topts.add('-gh', false, 'Print this help.  You did this to see this, so you probably understand.')\n\topts.add('-gpo', false, 'Print option values from all sources and the value used.')\n\topts.add('-gprint_gutconfig_sample', false, 'Print out json that can be used to make a gutconfig file.')\n\n\t# run as in editor, for shelling out purposes through Editor.\n\tvar o = opts.add('-graie', false, 'do not use')\n\to.show_in_help = false\n\treturn opts\n\n\n# Parses options, applying them to the _tester or setting values\n# in the options struct.\nfunc extract_command_line_options(from, to):\n\tto.compact_mode = from.get_value_or_null('-gcompact_mode')\n\tto.config_file = from.get_value_or_null('-gconfig')\n\tto.dirs = from.get_value_or_null('-gdir')\n\tto.disable_colors =  from.get_value_or_null('-gdisable_colors')\n\tto.double_strategy = from.get_value_or_null('-gdouble_strategy')\n\tto.errors_do_not_cause_failure = from.get_value_or_null('-gerrors_do_not_cause_failure')\n\tto.hide_orphans = from.get_value_or_null('-ghide_orphans')\n\tto.ignore_pause = from.get_value_or_null('-gignore_pause')\n\tto.include_subdirs = from.get_value_or_null('-ginclude_subdirs')\n\tto.inner_class = from.get_value_or_null('-ginner_class')\n\tto.log_level = from.get_value_or_null('-glog')\n\tto.opacity = from.get_value_or_null('-gopacity')\n\tto.post_run_script = from.get_value_or_null('-gpost_run_script')\n\tto.pre_run_script = from.get_value_or_null('-gpre_run_script')\n\tto.prefix = from.get_value_or_null('-gprefix')\n\tto.selected = from.get_value_or_null('-gselect')\n\tto.should_exit = from.get_value_or_null('-gexit')\n\tto.should_exit_on_success = from.get_value_or_null('-gexit_on_success')\n\tto.should_maximize = from.get_value_or_null('-gmaximize')\n\tto.suffix = from.get_value_or_null('-gsuffix')\n\tto.tests = from.get_value_or_null('-gtest')\n\tto.unit_test_name = from.get_value_or_null('-gunit_test_name')\n\tto.wait_log_delay = from.get_value_or_null('-gwait_log_delay')\n\n\tto.background_color = from.get_value_or_null('-gbackground_color')\n\tto.font_color = from.get_value_or_null('-gfont_color')\n\tto.font_name = from.get_value_or_null('-gfont_name')\n\tto.font_size = from.get_value_or_null('-gfont_size')\n\tto.paint_after = from.get_value_or_null('-gpaint_after')\n\n\tto.junit_xml_file = from.get_value_or_null('-gjunit_xml_file')\n\tto.junit_xml_timestamp = from.get_value_or_null('-gjunit_xml_timestamp')\n\n\tto.failure_error_types = from.get_value_or_null('-gfailure_error_types')\n\tto.no_error_tracking = from.get_value_or_null('-gno_error_tracking')\n\tto.raie = from.get_value_or_null('-graie')\n\n\n\nfunc _print_gutconfigs(values):\n\tvar header = \"\"\"Here is a sample of a full .gutconfig.json file.\nYou do not need to specify all values in your own file.  The values supplied in\nthis sample are what would be used if you ran gut w/o the -gprint_gutconfig_sample\noption.   Option priority is:  command-line, .gutconfig, default).\"\"\"\n\tprint(\"\\n\", header.replace(\"\\n\", ' '), \"\\n\")\n\tvar resolved = values\n\n\t# remove_at some options that don't make sense to be in config\n\tresolved.erase(\"config_file\")\n\tresolved.erase(\"show_help\")\n\n\tprint(JSON.stringify(resolved, '  '))\n\n\tfor key in resolved:\n\t\tresolved[key] = null\n\n\tprint(\"\\n\\nAnd here's an empty config for you fill in what you want.\")\n\tprint(JSON.stringify(resolved, ' '))\n\n\nfunc _run_tests(opt_resolver):\n\t_final_opts = opt_resolver.get_resolved_values();\n\t_gut_config.options = _final_opts\n\n\tvar runner = GutRunner.instantiate()\n\trunner.set_gut_config(_gut_config)\n\tget_tree().root.add_child(runner)\n\n\tif(opt_resolver.cmd_opts.raie):\n\t\trunner.run_from_editor()\n\telse:\n\t\trunner.run_tests()\n\n\n# parse options and run Gut\nfunc main():\n\tvar opt_resolver = OptionResolver.new()\n\topt_resolver.set_base_opts(_gut_config.default_options)\n\n\tvar cli_opts = setup_options(_gut_config.default_options, _gut_config.valid_fonts)\n\n\tcli_opts.parse()\n\tvar all_options_valid = cli_opts.unused.size() == 0\n\textract_command_line_options(cli_opts, opt_resolver.cmd_opts)\n\n\tvar config_path = opt_resolver.get_value('config_file')\n\tvar load_result = 1\n\t# Checking for an empty config path allows us to not use a config file via\n\t# the -gconfig_file option since using \"-gconfig_file=\" or -gconfig_file=''\"\n\t# will result in an empty string.\n\tif(config_path != ''):\n\t\tload_result = _gut_config.load_options_no_defaults(config_path)\n\n\t# SHORTCIRCUIT\n\tif(!all_options_valid):\n\t\tprint('Unknown arguments:  ', cli_opts.unused)\n\t\tget_tree().quit(1)\n\telif(load_result == -1):\n\t\tprint('Invalid gutconfig ', load_result)\n\t\tget_tree().quit(1)\n\telse:\n\t\topt_resolver.config_opts = _gut_config.options\n\n\t\tif(cli_opts.get_value('-gh')):\n\t\t\tprint(GutUtils.version_numbers.get_version_text())\n\t\t\tcli_opts.print_help()\n\t\t\tget_tree().quit(0)\n\t\telif(cli_opts.get_value('-gpo')):\n\t\t\tprint('All config options and where they are specified.  ' +\n\t\t\t\t'The \"final\" value shows which value will actually be used ' +\n\t\t\t\t'based on order of precedence (default < .gutconfig < cmd line).' + \"\\n\")\n\t\t\tprint(opt_resolver.to_s_verbose())\n\t\t\tget_tree().quit(0)\n\t\telif(cli_opts.get_value('-gprint_gutconfig_sample')):\n\t\t\t_print_gutconfigs(opt_resolver.get_resolved_values())\n\t\t\tget_tree().quit(0)\n\t\telse:\n\t\t\t_run_tests(opt_resolver)\n\n\n\n# ##############################################################################\n#(G)odot (U)nit (T)est class\n#\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n"
  },
  {
    "path": "demo/addons/gut/cli/gut_cli.gd.uid",
    "content": "uid://bhuudqinp4bth\n"
  },
  {
    "path": "demo/addons/gut/cli/optparse.gd",
    "content": "## Parses command line arguments, as one might expect.\n##\n## Parses command line arguments with a bunch of options including generating\n## text that displays all the arguments your script accepts.  This\n## is included in the GUT ClassRef since it might be usable by others and is\n## portable (everything it needs is in this one file).\n## [br]\n## This does alot, if you want to see it in action have a look at\n##\t[url=https://github.com/bitwes/Gut/blob/main/scratch/optparse_example.gd]scratch/optparse_example.gd[/url]\n## [codeblock lang=text]\n##\n## Godot Argument Lists\n## -------------------------\n## There are two sets of command line arguments that Godot populates:\n##\tOS.get_cmdline_args\n##\tOS.get_cmdline_user_args.\n##\n## OS.get_cmdline_args contains any arguments that are not used by the engine\n## itself.  This means options like --help and -d will never appear in this list\n## since these are used by the engine.  The one exception is the -s option which\n## is always included as the first entry and the script path as the second.\n## Optparse ignores these values for argument processing but can be accessed\n## with my_optparse.options.script_option.  This list does not contain any\n## arguments that appear in OS.get_cmdline_user_args.\n##\n## OS.get_cmdline_user_args contains any arguments that appear on the command\n## line AFTER \" -- \" or \" ++ \".  This list CAN contain options that the engine\n## would otherwise use, and are ignored completely by the engine.\n##\n## The parse method, by default, includes arguments from OS.get_cmdline_args and\n## OS.get_cmdline_user_args.  You can optionally pass one of these to the parse\n## method to limit which arguments are parsed.  You can also conjure up your own\n## array of arguments and pass that to parse.\n##\n## See Godot's documentation for get_cmdline_args and get_cmdline_user_args for\n## more information.\n##\n##\n## Adding Options\n## --------------\n## Use the following to add options to be parsed.  These methods return the\n## created Option instance.  See that class above for more info.  You can use\n## the returned instance to get values, or use get_value/get_value_or_null.\n##   add(\"--name\", \"default\", \"Description goes here\")\n##   add([\"--name\", \"--aliases\"], \"default\", \"Description goes here\")\n##   add_required([\"--name\", \"--aliases\"], \"default\", \"Description goes here\")\n##   add_positional(\"--name\", \"default\", \"Description goes here\")\n##   add_positional_required(\"--name\", \"default\", \"Description goes here\")\n##\n## get_value will return the value of the option or the default if it was not\n## set.  get_value_or_null will return the value of the option or null if it was\n## not set.\n##\n## The Datatype for an option is determined from the default value supplied to\n## the various add methods.  Supported types are\n##   String\n##   Int\n##   Float\n##   Array of strings\n##   Boolean\n##\n##\n## Value Parsing\n## -------------\n## optparse uses option_name_prefix to differentiate between option names and\n## values.  Any argument that starts with this value will be treated as an\n## argument name.  The default is \"-\".  Set this before calling parse if you want\n## to change it.\n##\n## Values for options can be supplied on the command line with or without an \"=\":\n##\toption=value    # no space around \"=\"\n##\toption value    # a space between option and value w/o =\n## There is no way to escape \"=\" at this time.\n##\n## Array options can be specified multiple times and/or set from a comma delimited\n## list.\n##   -gdir=a,b\n##   -gdir c,d\n##   -gdir e\n## Results in -gdir equaling [a, b, c, d, e].  There is no way to escape commas\n## at this time.\n##\n## To specify an empty list via the command line follow the option with an equal\n## sign\n##   -gdir=\n##\n## Boolean options will have thier value set to !default when they are supplied\n## on the command line.  Boolean options cannot have a value on the command line.\n## They are either supplied or not.\n##\n## If a value is not an array and is specified multiple times on the command line\n## then the last entry will be used as the value.\n##\n## Positional argument values are parsed after all named arguments are parsed.\n## This means that other options can appear before, between, and after positional\n## arguments.\n##   --foo=bar positional_0_value --disabled --bar foo positional_1_value --a_flag\n##\n## Anything that is not used by named or positional arguments will appear in the\n## unused property.  You can use this to detect unrecognized arguments or treat\n## everything else provided as a list of things, or whatever you want.  You can\n## use is_option on the elements of unused (or whatever you want really) to see\n## if optparse would treat it as an option name.\n##\n## Use get_missing_required_options to get an array of Option with all required\n## options that were not found when parsing.\n##\n## The parsed_args property holds the list of arguments that were parsed.\n##\n##\n## Help Generation\n## ---------------\n## You can call get_help to generate help text, or you can just call print_help\n## and this will print it for you.\n##\n## Set the banner property to any text you want to appear before the usage and\n## options sections.\n##\n## Options are printed in the order they are added.  You can add a heading for\n## different options sections with add_heading.\n##   add(\"--asdf\", 1, \"This will have no heading\")\n##   add_heading(\"foo\")\n##   add(\"--foo\", false, \"This will have the foo heading\")\n##   add(\"--another_foo\", 1.5, \"This too.\")\n##   add_heading(\"This is after foo\")\n##   add(\"--bar\", true, \"You probably get it by now.\")\n##\n## If you include \"[default]\" in the description of a option, then the help will\n## substitue it with the default value.\n## [/codeblock]\n\n\n#-------------------------------------------------------------------------------\n# Holds all the properties of a command line option\n#\n# value will return the default when it has not been set.\n#-------------------------------------------------------------------------------\nclass Option:\n\tvar _has_been_set = false\n\tvar _value = null\n\t# REMEMBER that when this option is an array, you have to set the value\n\t# before you alter the contents of the array (append etc) or has_been_set\n\t# will return false and it might not be used right.  For example\n\t# get_value_or_null will return null when you've actually changed the value.\n\tvar value = _value:\n\t\tget:\n\t\t\treturn _value\n\n\t\tset(val):\n\t\t\t_has_been_set = true\n\t\t\t_value = val\n\n\tvar option_name = ''\n\tvar default = null\n\tvar description = ''\n\tvar required = false\n\tvar aliases: Array[String] = []\n\tvar show_in_help = true\n\n\n\tfunc _init(name,default_value,desc=''):\n\t\toption_name = name\n\t\tdefault = default_value\n\t\tdescription = desc\n\t\t_value = default\n\n\n\tfunc wrap_text(text, left_indent, max_length, wiggle_room=15):\n\t\tvar line_indent = str(\"\\n\", \" \".repeat(left_indent + 1))\n\t\tvar wrapped = ''\n\t\tvar position = 0\n\t\tvar split_length = max_length\n\t\twhile(position < text.length()):\n\t\t\tif(position > 0):\n\t\t\t\twrapped += line_indent\n\n\t\t\tvar split_by = split_length\n\t\t\tif(position + split_by + wiggle_room >= text.length()):\n\t\t\t\tsplit_by = text.length() - position\n\t\t\telse:\n\t\t\t\tvar min_space = text.rfind(' ', position + split_length)\n\t\t\t\tvar max_space = text.find(' ', position + split_length)\n\t\t\t\tif(max_space <= position + split_length + wiggle_room):\n\t\t\t\t\tsplit_by = max_space - position\n\t\t\t\telse:\n\t\t\t\t\tsplit_by = min_space - position\n\n\t\t\twrapped += text.substr(position, split_by).lstrip(' ')\n\n\t\t\tif(position == 0):\n\t\t\t\tsplit_length = max_length - left_indent\n\n\t\t\tposition += split_by\n\n\n\t\treturn wrapped\n\n\n\n\tfunc to_s(min_space=0, wrap_length=100):\n\t\tvar line_indent = str(\"\\n\", \" \".repeat(min_space + 1))\n\t\tvar subbed_desc = description\n\t\tif not aliases.is_empty():\n\t\t\tsubbed_desc += \"\\naliases: \" + \", \".join(aliases)\n\t\tsubbed_desc = subbed_desc.replace('[default]', str(default))\n\t\tsubbed_desc = subbed_desc.replace(\"\\n\", line_indent)\n\n\t\tvar final = str(option_name.rpad(min_space), ' ', subbed_desc)\n\t\tif(wrap_length != -1):\n\t\t\tfinal = wrap_text(final, min_space, wrap_length)\n\n\t\treturn final\n\n\n\tfunc has_been_set():\n\t\treturn _has_been_set\n\n\n\n\n#-------------------------------------------------------------------------------\n# A struct for organizing options by a heading\n#-------------------------------------------------------------------------------\nclass OptionHeading:\n\tvar options = []\n\tvar display = 'default'\n\n\n\n\n#-------------------------------------------------------------------------------\n# Organizes options by order, heading, position.  Also responsible for all\n# help related text generation.\n#-------------------------------------------------------------------------------\nclass Options:\n\tvar options = []\n\tvar positional = []\n\tvar default_heading = OptionHeading.new()\n\tvar script_option = Option.new('-s', '?', 'script option provided by Godot')\n\n\tvar _options_by_name = {\"--script\": script_option, \"-s\": script_option}\n\tvar _options_by_heading = [default_heading]\n\tvar _cur_heading = default_heading\n\n\n\tfunc add_heading(display):\n\t\tvar heading = OptionHeading.new()\n\t\theading.display = display\n\t\t_cur_heading = heading\n\t\t_options_by_heading.append(heading)\n\n\n\tfunc add(option, aliases=null):\n\t\toptions.append(option)\n\t\t_options_by_name[option.option_name] = option\n\t\t_cur_heading.options.append(option)\n\n\t\tif aliases != null:\n\t\t\tfor a in aliases:\n\t\t\t\t_options_by_name[a] = option\n\t\t\toption.aliases.assign(aliases)\n\n\n\tfunc add_positional(option):\n\t\tpositional.append(option)\n\t\t_options_by_name[option.option_name] = option\n\n\n\tfunc get_by_name(option_name):\n\t\tvar found_param = null\n\t\tif(_options_by_name.has(option_name)):\n\t\t\tfound_param = _options_by_name[option_name]\n\n\t\treturn found_param\n\n\n\tfunc get_help_text():\n\t\tvar longest = 0\n\t\tvar text = \"\"\n\t\tfor i in range(options.size()):\n\t\t\tif(options[i].option_name.length() > longest):\n\t\t\t\tlongest = options[i].option_name.length()\n\n\t\tfor heading in _options_by_heading:\n\t\t\tif(heading != default_heading):\n\t\t\t\ttext += str(\"\\n\", heading.display, \"\\n\")\n\t\t\tfor option in heading.options:\n\t\t\t\tif(option.show_in_help):\n\t\t\t\t\ttext += str('  ', option.to_s(longest + 2).replace(\"\\n\", \"\\n  \"), \"\\n\")\n\n\t\treturn text\n\n\n\tfunc get_option_value_text():\n\t\tvar text = \"\"\n\t\tvar i = 0\n\t\tfor option in positional:\n\t\t\ttext += str(i, '.  ', option.option_name, ' = ', option.value)\n\n\t\t\tif(!option.has_been_set()):\n\t\t\t\ttext += \" (default)\"\n\t\t\ttext += \"\\n\"\n\t\t\ti += 1\n\n\t\tfor option in options:\n\t\t\ttext += str(option.option_name, ' = ', option.value)\n\n\t\t\tif(!option.has_been_set()):\n\t\t\t\ttext += \" (default)\"\n\t\t\ttext += \"\\n\"\n\t\treturn text\n\n\n\tfunc print_option_values():\n\t\tprint(get_option_value_text())\n\n\n\tfunc get_missing_required_options():\n\t\tvar to_return = []\n\t\tfor opt in options:\n\t\t\tif(opt.required and !opt.has_been_set()):\n\t\t\t\tto_return.append(opt)\n\n\t\tfor opt in positional:\n\t\t\tif(opt.required and !opt.has_been_set()):\n\t\t\t\tto_return.append(opt)\n\n\t\treturn to_return\n\n\n\tfunc get_usage_text():\n\t\tvar pos_text = \"\"\n\t\tfor opt in positional:\n\t\t\tpos_text += str(\"[\", opt.description, \"] \")\n\n\t\tif(pos_text != \"\"):\n\t\t\tpos_text += \" [opts] \"\n\n\t\treturn \"<path to godot> -s \" + script_option.value + \" [opts] \" + pos_text\n\n\n\n\n#-------------------------------------------------------------------------------\n#\n# optarse\n#\n#-------------------------------------------------------------------------------\n## @ignore\nvar options := Options.new()\n## Set the banner property to any text you want to appear before the usage and\n## options sections when printing the options help.\nvar banner := ''\n## optparse uses option_name_prefix to differentiate between option names and\n## values.  Any argument that starts with this value will be treated as an\n## argument name.  The default is \"-\".  Set this before calling parse if you want\n## to change it.\nvar option_name_prefix := '-'\n## @ignore\nvar unused = []\n## @ignore\nvar parsed_args = []\n## @ignore\nvar values: Dictionary = {}\n\n\nfunc _populate_values_dictionary():\n\tfor entry in options.options:\n\t\tvar value_key = entry.option_name.lstrip('-')\n\t\tvalues[value_key] = entry.value\n\n\tfor entry in options.positional:\n\t\tvar value_key = entry.option_name.lstrip('-')\n\t\tvalues[value_key] = entry.value\n\n\nfunc _convert_value_to_array(raw_value):\n\tvar split = raw_value.split(',')\n\t# This is what an empty set looks like from the command line.  If we do\n\t# not do this then we will always get back [''] which is not what it\n\t# shoudl be.\n\tif(split.size() == 1 and split[0] == ''):\n\t\tsplit = []\n\treturn split\n\n# REMEMBER raw_value not used for bools.\nfunc _set_option_value(option, raw_value):\n\tvar t = typeof(option.default)\n\t# only set values that were specified at the command line so that\n\t# we can punch through default and config values correctly later.\n\t# Without this check, you can't tell the difference between the\n\t# defaults and what was specified, so you can't punch through\n\t# higher level options.\n\tif(t == TYPE_INT):\n\t\toption.value = int(raw_value)\n\telif(t == TYPE_STRING):\n\t\toption.value = str(raw_value)\n\telif(t == TYPE_ARRAY):\n\t\tvar values = _convert_value_to_array(raw_value)\n\t\tif(!option.has_been_set()):\n\t\t\toption.value = []\n\t\toption.value.append_array(values)\n\telif(t == TYPE_BOOL):\n\t\toption.value = !option.default\n\telif(t == TYPE_FLOAT):\n\t\toption.value = float(raw_value)\n\telif(t == TYPE_NIL):\n\t\tprint(option.option_name + ' cannot be processed, it has a nil datatype')\n\telse:\n\t\tprint(option.option_name + ' cannot be processed, it has unknown datatype:' + str(t))\n\n\nfunc _parse_command_line_arguments(args):\n\tvar parsed_opts = args.duplicate()\n\tvar i = 0\n\tvar positional_index = 0\n\n\twhile i < parsed_opts.size():\n\t\tvar opt  = ''\n\t\tvar value = ''\n\t\tvar entry = parsed_opts[i]\n\n\t\tif(is_option(entry)):\n\t\t\tif(entry.find('=') != -1):\n\t\t\t\tvar parts = entry.split('=')\n\t\t\t\topt = parts[0]\n\t\t\t\tvalue = parts[1]\n\t\t\t\tvar the_option = options.get_by_name(opt)\n\t\t\t\tif(the_option != null):\n\t\t\t\t\tparsed_opts.remove_at(i)\n\t\t\t\t\t_set_option_value(the_option, value)\n\t\t\t\telse:\n\t\t\t\t\ti += 1\n\t\t\telse:\n\t\t\t\tvar the_option = options.get_by_name(entry)\n\t\t\t\tif(the_option != null):\n\t\t\t\t\tparsed_opts.remove_at(i)\n\t\t\t\t\tif(typeof(the_option.default) == TYPE_BOOL):\n\t\t\t\t\t\t_set_option_value(the_option, null)\n\t\t\t\t\telif(i < parsed_opts.size() and !is_option(parsed_opts[i])):\n\t\t\t\t\t\tvalue = parsed_opts[i]\n\t\t\t\t\t\tparsed_opts.remove_at(i)\n\t\t\t\t\t\t_set_option_value(the_option, value)\n\t\t\t\telse:\n\t\t\t\t\ti += 1\n\t\telse:\n\t\t\tif(positional_index < options.positional.size()):\n\t\t\t\t_set_option_value(options.positional[positional_index], entry)\n\t\t\t\tparsed_opts.remove_at(i)\n\t\t\t\tpositional_index += 1\n\t\t\telse:\n\t\t\t\ti += 1\n\n\t# this is the leftovers that were not extracted.\n\treturn parsed_opts\n\n\n## Test if something is an existing argument. If [code]str(arg)[/code] begins\n## with the [member option_name_prefix], it will considered true,\n## otherwise it will be considered false.\nfunc is_option(arg) -> bool:\n\treturn str(arg).begins_with(option_name_prefix)\n\n\n## Adds a command line option.\n## If [param op_names] is a String, this is set as the argument's name.\n## If [param op_names] is an Array of Strings, all elements of the array\n## will be aliases for the same argument and will be treated as such during\n## parsing.\n## [param default] is the default value the option will be set to if it is not\n## explicitly set during parsing.\n## [param desc] is a human readable text description of the option.\n## If the option is successfully added, the Option object will be returned.\n## If the option is not successfully added (e.g. a name collision with another\n## option occurs), an error message will be printed and [code]null[/code]\n## will be returned.\nfunc add(op_names, default, desc: String) -> Option:\n\tvar op_name: String\n\tvar aliases: Array[String] = []\n\tvar new_op: Option = null\n\n\tif(typeof(op_names) == TYPE_STRING):\n\t\top_name = op_names\n\telse:\n\t\top_name = op_names[0]\n\t\taliases.assign(op_names.slice(1))\n\n\tvar bad_alias: int = aliases.map(\n\t\tfunc (a: String) -> bool: return options.get_by_name(a) != null\n\t).find(true)\n\n\tif(options.get_by_name(op_name) != null):\n\t\tpush_error(str('Option [', op_name, '] already exists.'))\n\telif bad_alias != -1:\n\t\tpush_error(str('Option [', aliases[bad_alias], '] already exists.'))\n\telse:\n\t\tnew_op = Option.new(op_name, default, desc)\n\t\toptions.add(new_op, aliases)\n\n\treturn new_op\n\n\n## Adds a required command line option.\n## Required options that have not been set may be collected after parsing\n## by calling [method get_missing_required_options].\n## If [param op_names] is a String, this is set as the argument's name.\n## If [param op_names] is an Array of Strings, all elements of the array\n## will be aliases for the same argument and will be treated as such during\n## parsing.\n## [param default] is the default value the option will be set to if it is not\n## explicitly set during parsing.\n## [param desc] is a human readable text description of the option.\n## If the option is successfully added, the Option object will be returned.\n## If the option is not successfully added (e.g. a name collision with another\n## option occurs), an error message will be printed and [code]null[/code]\n## will be returned.\nfunc add_required(op_names, default, desc: String) -> Option:\n\tvar op := add(op_names, default, desc)\n\tif(op != null):\n\t\top.required = true\n\treturn op\n\n\n## Adds a positional command line option.\n## Positional options are parsed by their position in the list of arguments\n## are are not assigned by name by the user.\n## If [param op_name] is a String, this is set as the argument's name.\n## If [param op_name] is an Array of Strings, all elements of the array\n## will be aliases for the same argument and will be treated as such during\n## parsing.\n## [param default] is the default value the option will be set to if it is not\n## explicitly set during parsing.\n## [param desc] is a human readable text description of the option.\n## If the option is successfully added, the Option object will be returned.\n## If the option is not successfully added (e.g. a name collision with another\n## option occurs), an error message will be printed and [code]null[/code]\n## will be returned.\nfunc add_positional(op_name, default, desc: String) -> Option:\n\tvar new_op = null\n\tif(options.get_by_name(op_name) != null):\n\t\tpush_error(str('Positional option [', op_name, '] already exists.'))\n\telse:\n\t\tnew_op = Option.new(op_name, default, desc)\n\t\toptions.add_positional(new_op)\n\treturn new_op\n\n\n## Adds a required positional command line option.\n## If [param op_name] is a String, this is set as the argument's name.\n## Required options that have not been set may be collected after parsing\n## by calling [method get_missing_required_options].\n## Positional options are parsed by their position in the list of arguments\n## are are not assigned by name by the user.\n## If [param op_name] is an Array of Strings, all elements of the array\n## will be aliases for the same argument and will be treated as such during\n## parsing.\n## [param default] is the default value the option will be set to if it is not\n## explicitly set during parsing.\n## [param desc] is a human readable text description of the option.\n## If the option is successfully added, the Option object will be returned.\n## If the option is not successfully added (e.g. a name collision with another\n## option occurs), an error message will be printed and [code]null[/code]\n## will be returned.\nfunc add_positional_required(op_name, default, desc: String) -> Option:\n\tvar op = add_positional(op_name, default, desc)\n\tif(op != null):\n\t\top.required = true\n\treturn op\n\n\n## Headings are used to separate logical groups of command line options\n## when printing out options from the help menu.\n## Headings are printed out between option descriptions in the order\n## that [method add_heading] was called.\nfunc add_heading(display_text: String) -> void:\n\toptions.add_heading(display_text)\n\n\n## Gets the value assigned to an option after parsing.\n## [param name] can be the name of the option or an alias of it.\n## [param name] specifies the option whose value you wish to query.\n## If the option exists, the value assigned to it during parsing is returned.\n## Otherwise, an error message is printed and [code]null[/code] is returned.\nfunc get_value(name: String):\n\tvar found_param: Option = options.get_by_name(name)\n\n\tif(found_param != null):\n\t\treturn found_param.value\n\telse:\n\t\tpush_error(\"COULD NOT FIND OPTION \" + name)\n\t\treturn null\n\n\n## Gets the value assigned to an option after parsing,\n## returning null if the option was not assigned instead of its default value.\n## [param name] specifies the option whose value you wish to query.\n## This can be useful when providing an order of precedence to your values.\n## For example if\n## [codeblock]\n##     default value < config file < command line\n## [/codeblock]\n## then you do not want to get the default value for a command line option or\n## it will overwrite the value in a config file.\nfunc get_value_or_null(name: String):\n\tvar found_param: Option = options.get_by_name(name)\n\n\tif(found_param != null and found_param.has_been_set()):\n\t\treturn found_param.value\n\telse:\n\t\treturn null\n\n\n## Returns the help text for all defined options.\nfunc get_help() -> String:\n\tvar sep := '---------------------------------------------------------'\n\n\tvar text := str(sep, \"\\n\", banner, \"\\n\\n\")\n\ttext += \"Usage\\n-----------\\n\"\n\ttext += \"  \" + options.get_usage_text() + \"\\n\\n\"\n\ttext += \"\\nOptions\\n-----------\\n\"\n\ttext += options.get_help_text()\n\ttext += str(sep, \"\\n\")\n\treturn text\n\n\n## Prints out the help text for all defined options.\nfunc print_help() -> void:\n\tprint(get_help())\n\n\n## Parses a string for all options that have been set in this optparse.\n## if [param cli_args] is passed as a String, then it is parsed.\n## Otherwise if [param cli_args] is null,\n## aruments passed to the Godot engine at startup are parsed.\n## See the explanation at the top of addons/gut/cli/optparse.gd to understand\n## which arguments this will have access to.\nfunc parse(cli_args=null) -> void:\n\tparsed_args = cli_args\n\n\tif(parsed_args == null):\n\t\tparsed_args = OS.get_cmdline_args()\n\t\tparsed_args.append_array(OS.get_cmdline_user_args())\n\n\tunused = _parse_command_line_arguments(parsed_args)\n\t_populate_values_dictionary()\n\n\n## Get all options that were required and were not set during parsing.\n## The return value is an Array of Options.\nfunc get_missing_required_options() -> Array:\n\treturn options.get_missing_required_options()\n\n\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################"
  },
  {
    "path": "demo/addons/gut/cli/optparse.gd.uid",
    "content": "uid://c8m4fojwln6bq\n"
  },
  {
    "path": "demo/addons/gut/collected_script.gd",
    "content": "# ------------------------------------------------------------------------------\n# This holds all the meta information for a test script.  It contains the\n# name of the inner class and an array of CollectedTests.  This does not parse\n# anything, it just holds the data about parsed scripts and tests.  The\n# TestCollector is responsible for populating this object.\n#\n# This class also facilitates all the exporting and importing of tests.\n# ------------------------------------------------------------------------------\nvar CollectedTest = GutUtils.CollectedTest\n\nvar _lgr = null\n\n# One entry per test found in the script.  Added externally by TestCollector\nvar tests = []\n# One entry for before_all and after_all (maybe add before_each and after_each).\n# These are added by Gut when running before_all and after_all for the script.\nvar setup_teardown_tests = []\nvar inner_class_name:StringName\nvar path:String\n\n\n# Set externally by test_collector after it can verify that the script was\n# actually loaded.  This could probably be changed to just hold the GutTest\n# script that was loaded, cutting down on complexity elsewhere.\nvar is_loaded = false\n\n# Set by Gut when it decides that a script should be skipped.\n# Right now this is whenever the script has the variable skip_script declared.\n# the value of skip_script is put into skip_reason.\nvar was_skipped = false\nvar skip_reason = ''\nvar was_run = false\n\n\nvar name = '' :\n\tget: return path\n\tset(val):pass\n\n\nfunc _init(logger=null):\n\t_lgr = logger\n\n\nfunc get_new():\n\tvar inst = load_script().new()\n\tinst.collected_script = self\n\treturn inst\n\n\nfunc load_script():\n\tvar to_return = load(path)\n\n\tif(inner_class_name != null and inner_class_name != ''):\n\t\t# If we wanted to do inner classes in inner classses\n\t\t# then this would have to become some kind of loop or recursive\n\t\t# call to go all the way down the chain or this class would\n\t\t# have to change to hold onto the loaded class instead of\n\t\t# just path information.\n\t\tto_return = to_return.get(inner_class_name)\n\n\treturn to_return\n\n# script.gd.InnerClass\nfunc get_filename_and_inner():\n\tvar to_return = get_filename()\n\tif(inner_class_name != ''):\n\t\tto_return += '.' + String(inner_class_name)\n\treturn to_return\n\n\n# res://foo/bar.gd.FooBar\nfunc get_full_name():\n\tvar to_return = path\n\tif(inner_class_name != ''):\n\t\tto_return += '.' + String(inner_class_name)\n\treturn to_return\n\n\nfunc get_filename():\n\treturn path.get_file()\n\n\nfunc has_inner_class():\n\treturn inner_class_name != ''\n\n\n# Note:  although this no longer needs to export the inner_class names since\n#        they are pulled from metadata now, it is easier to leave that in\n#        so we don't have to cut the export down to unique script names.\nfunc export_to(config_file, section):\n\tconfig_file.set_value(section, 'path', path)\n\tconfig_file.set_value(section, 'inner_class', inner_class_name)\n\tvar names = []\n\tfor i in range(tests.size()):\n\t\tnames.append(tests[i].name)\n\tconfig_file.set_value(section, 'tests', names)\n\n\nfunc _remap_path(source_path):\n\tvar to_return = source_path\n\tif(!FileAccess.file_exists(source_path)):\n\t\t_lgr.debug('Checking for remap for:  ' + source_path)\n\t\tvar remap_path = source_path.get_basename() + '.gd.remap'\n\t\tif(FileAccess.file_exists(remap_path)):\n\t\t\tvar cf = ConfigFile.new()\n\t\t\tcf.load(remap_path)\n\t\t\tto_return = cf.get_value('remap', 'path')\n\t\telse:\n\t\t\t_lgr.warn('Could not find remap file ' + remap_path)\n\treturn to_return\n\n\nfunc import_from(config_file, section):\n\tpath = config_file.get_value(section, 'path')\n\tpath = _remap_path(path)\n\t# Null is an acceptable value, but you can't pass null as a default to\n\t# get_value since it thinks you didn't send a default...then it spits\n\t# out red text.  This works around that.\n\tvar inner_name = config_file.get_value(section, 'inner_class', 'Placeholder')\n\tif(inner_name != 'Placeholder'):\n\t\tinner_class_name = inner_name\n\telse: # just being explicit\n\t\tinner_class_name = StringName(\"\")\n\n\nfunc get_test_named(test_name):\n\treturn GutUtils.search_array(tests, 'name', test_name)\n\n\nfunc get_ran_test_count():\n\tvar count = 0\n\tfor t in tests:\n\t\tif(t.was_run):\n\t\t\tcount += 1\n\treturn count\n\n\nfunc get_assert_count():\n\tvar count = 0\n\tfor t in tests:\n\t\tcount += t.pass_texts.size()\n\t\tcount += t.fail_texts.size()\n\tfor t in setup_teardown_tests:\n\t\tcount += t.pass_texts.size()\n\t\tcount += t.fail_texts.size()\n\treturn count\n\n\nfunc get_pass_count():\n\tvar count = 0\n\tfor t in tests:\n\t\tcount += t.pass_texts.size()\n\tfor t in setup_teardown_tests:\n\t\tcount += t.pass_texts.size()\n\treturn count\n\n\nfunc get_fail_count():\n\tvar count = 0\n\tfor t in tests:\n\t\tcount += t.fail_texts.size()\n\tfor t in setup_teardown_tests:\n\t\tcount += t.fail_texts.size()\n\treturn count\n\n\nfunc get_pending_count():\n\tvar count = 0\n\tfor t in tests:\n\t\tcount += t.pending_texts.size()\n\treturn count\n\n\nfunc get_passing_test_count():\n\tvar count = 0\n\tfor t in tests:\n\t\tif(t.is_passing()):\n\t\t\tcount += 1\n\treturn count\n\n\nfunc get_failing_test_count():\n\tvar count = 0\n\tfor t in tests:\n\t\tif(t.is_failing()):\n\t\t\tcount += 1\n\treturn count\n\n\nfunc get_risky_count():\n\tvar count = 0\n\tif(was_skipped):\n\t\tcount = 1\n\telse:\n\t\tfor t in tests:\n\t\t\tif(t.is_risky()):\n\t\t\t\tcount += 1\n\treturn count\n\n\nfunc to_s():\n\tvar to_return = path\n\tif(inner_class_name != null):\n\t\tto_return += str('.', inner_class_name)\n\tto_return += \"\\n\"\n\tfor i in range(tests.size()):\n\t\tto_return += str('  ', tests[i].to_s())\n\treturn to_return\n"
  },
  {
    "path": "demo/addons/gut/collected_script.gd.uid",
    "content": "uid://bjjcnr1oqvag6\n"
  },
  {
    "path": "demo/addons/gut/collected_test.gd",
    "content": "# ------------------------------------------------------------------------------\n# Used to keep track of info about each test ran.\n# ------------------------------------------------------------------------------\n# the name of the function\nvar name = \"\"\n\n# flag to know if the name has been printed yet.  Used by the logger.\nvar has_printed_name = false\n\n# the number of arguments the method has\nvar arg_count = 0\n\n# the time it took to execute the test in seconds\nvar time_taken : float = 0\n\n# The number of asserts in the test.  Converted to a property for backwards\n# compatibility.  This now reflects the text sizes instead of being a value\n# that can be altered externally.\nvar assert_count = 0 :\n\tget: return pass_texts.size() + fail_texts.size()\n\tset(val): pass\n\n# Converted to propety for backwards compatibility.  This now cannot be set\n# externally\nvar pending = false :\n\tget: return is_pending()\n\tset(val): pass\n\n# the line number when the test fails\nvar line_number = -1\n\n# Set internally by Gut using whatever reason Gut wants to use to set this.\n# Gut will skip these marked true and the test will be listed as risky.\nvar should_skip = false  # -- Currently not used by GUT don't believe ^\n\nvar pass_texts = []\nvar fail_texts = []\nvar pending_texts = []\nvar orphans = 0\n\nvar was_run = false\n\nvar collected_script : WeakRef = null\n\n\nfunc did_pass():\n\treturn is_passing()\n\n\nfunc add_fail(fail_text):\n\tfail_texts.append(fail_text)\n\n\nfunc add_pending(pending_text):\n\tpending_texts.append(pending_text)\n\n\nfunc add_pass(passing_text):\n\tpass_texts.append(passing_text)\n\n\n# must have passed an assert and not have any other status to be passing\nfunc is_passing():\n\treturn pass_texts.size() > 0 and fail_texts.size() == 0 and pending_texts.size() == 0\n\n\n# failing takes precedence over everything else, so any failures makes the\n# test a failure.\nfunc is_failing():\n\treturn fail_texts.size() > 0\n\n\n# test is only pending if pending was called and the test is not failing.\nfunc is_pending():\n\treturn pending_texts.size() > 0 and fail_texts.size() == 0\n\n\nfunc is_risky():\n\treturn should_skip or (was_run and !did_something())\n\n\nfunc did_something():\n\treturn is_passing() or is_failing() or is_pending()\n\n\nfunc get_status_text():\n\tvar to_return = GutUtils.TEST_STATUSES.NO_ASSERTS\n\n\tif(should_skip):\n\t\tto_return = GutUtils.TEST_STATUSES.SKIPPED\n\telif(!was_run):\n\t\tto_return = GutUtils.TEST_STATUSES.NOT_RUN\n\telif(pending_texts.size() > 0):\n\t\tto_return = GutUtils.TEST_STATUSES.PENDING\n\telif(fail_texts.size() > 0):\n\t\tto_return = GutUtils.TEST_STATUSES.FAILED\n\telif(pass_texts.size() > 0):\n\t\tto_return = GutUtils.TEST_STATUSES.PASSED\n\n\treturn to_return\n\n\n# Deprecated\nfunc get_status():\n\treturn get_status_text()\n\n\nfunc to_s():\n\tvar pad = '     '\n\tvar to_return = str(name, \"[\", get_status_text(), \"]\\n\")\n\n\tfor i in range(fail_texts.size()):\n\t\tto_return += str(pad, 'Fail:  ', fail_texts[i])\n\tfor i in range(pending_texts.size()):\n\t\tto_return += str(pad, 'Pending:  ', pending_texts[i], \"\\n\")\n\tfor i in range(pass_texts.size()):\n\t\tto_return += str(pad, 'Pass:  ', pass_texts[i], \"\\n\")\n\treturn to_return\n"
  },
  {
    "path": "demo/addons/gut/collected_test.gd.uid",
    "content": "uid://cl854f1m26a2a\n"
  },
  {
    "path": "demo/addons/gut/comparator.gd",
    "content": "var _strutils = GutUtils.Strutils.new()\nvar _max_length = 100\nvar _should_compare_int_to_float = true\n\nconst MISSING = '|__missing__gut__compare__value__|'\n\n\nfunc _cannot_compare_text(v1, v2):\n\treturn str('Cannot compare ', _strutils.types[typeof(v1)], ' with ',\n\t\t_strutils.types[typeof(v2)], '.')\n\n\nfunc _make_missing_string(text):\n\treturn '<missing ' + text + '>'\n\n\nfunc _create_missing_result(v1, v2, text):\n\tvar to_return = null\n\tvar v1_str = format_value(v1)\n\tvar v2_str = format_value(v2)\n\n\tif(typeof(v1) == TYPE_STRING and v1 == MISSING):\n\t\tv1_str = _make_missing_string(text)\n\t\tto_return = GutUtils.CompareResult.new()\n\telif(typeof(v2) == TYPE_STRING and v2 == MISSING):\n\t\tv2_str = _make_missing_string(text)\n\t\tto_return = GutUtils.CompareResult.new()\n\n\tif(to_return != null):\n\t\tto_return.summary = str(v1_str, ' != ', v2_str)\n\t\tto_return.are_equal = false\n\n\treturn to_return\n\n\nfunc simple(v1, v2, missing_string=''):\n\tvar missing_result = _create_missing_result(v1, v2, missing_string)\n\tif(missing_result != null):\n\t\treturn missing_result\n\n\tvar result = GutUtils.CompareResult.new()\n\tvar cmp_str = null\n\tvar extra = ''\n\n\tvar tv1 = typeof(v1)\n\tvar tv2 = typeof(v2)\n\n\t# print(tv1, '::', tv2, '   ', _strutils.types[tv1], '::', _strutils.types[tv2])\n\tif(_should_compare_int_to_float and [TYPE_INT, TYPE_FLOAT].has(tv1) and [TYPE_INT, TYPE_FLOAT].has(tv2)):\n\t\tresult.are_equal = v1 == v2\n\telif([TYPE_STRING, TYPE_STRING_NAME].has(tv1) and [TYPE_STRING, TYPE_STRING_NAME].has(tv2)):\n\t\tresult.are_equal = v1 == v2\n\telif(GutUtils.are_datatypes_same(v1, v2)):\n\t\tresult.are_equal = v1 == v2\n\n\t\tif(typeof(v1) == TYPE_DICTIONARY or typeof(v1) == TYPE_ARRAY):\n\t\t\tvar sub_result = GutUtils.DiffTool.new(v1, v2, GutUtils.DIFF.DEEP)\n\t\t\tresult.summary = sub_result.get_short_summary()\n\t\t\tif(!sub_result.are_equal):\n\t\t\t\textra = \".\\n\" + sub_result.get_short_summary()\n\telse:\n\t\tcmp_str = '!='\n\t\tresult.are_equal = false\n\t\textra = str('.  ', _cannot_compare_text(v1, v2))\n\n\tcmp_str = get_compare_symbol(result.are_equal)\n\tresult.summary = str(format_value(v1), ' ', cmp_str, ' ', format_value(v2), extra)\n\n\treturn result\n\n\nfunc shallow(v1, v2):\n\tvar result =  null\n\tif(GutUtils.are_datatypes_same(v1, v2)):\n\t\tif(typeof(v1) in [TYPE_ARRAY, TYPE_DICTIONARY]):\n\t\t\tresult = GutUtils.DiffTool.new(v1, v2, GutUtils.DIFF.DEEP)\n\t\telse:\n\t\t\tresult = simple(v1, v2)\n\telse:\n\t\tresult = simple(v1, v2)\n\n\treturn result\n\n\nfunc deep(v1, v2):\n\tvar result =  null\n\n\tif(GutUtils.are_datatypes_same(v1, v2)):\n\t\tif(typeof(v1) in [TYPE_ARRAY, TYPE_DICTIONARY]):\n\t\t\tresult = GutUtils.DiffTool.new(v1, v2, GutUtils.DIFF.DEEP)\n\t\telse:\n\t\t\tresult = simple(v1, v2)\n\telse:\n\t\tresult = simple(v1, v2)\n\n\treturn result\n\n\nfunc format_value(val, max_val_length=_max_length):\n\treturn _strutils.truncate_string(_strutils.type2str(val), max_val_length)\n\n\nfunc compare(v1, v2, diff_type=GutUtils.DIFF.SIMPLE):\n\tvar result = null\n\tif(diff_type == GutUtils.DIFF.SIMPLE):\n\t\tresult = simple(v1, v2)\n\telif(diff_type ==  GutUtils.DIFF.DEEP):\n\t\tresult = deep(v1, v2)\n\n\treturn result\n\n\nfunc get_should_compare_int_to_float():\n\treturn _should_compare_int_to_float\n\n\nfunc set_should_compare_int_to_float(should_compare_int_float):\n\t_should_compare_int_to_float = should_compare_int_float\n\n\nfunc get_compare_symbol(is_equal):\n\tif(is_equal):\n\t\treturn '=='\n\telse:\n\t\treturn '!='\n"
  },
  {
    "path": "demo/addons/gut/comparator.gd.uid",
    "content": "uid://bohry7fhscy7y\n"
  },
  {
    "path": "demo/addons/gut/compare_result.gd",
    "content": "var _are_equal = false\nvar are_equal = false :\n\tget:\n\t\treturn get_are_equal()\n\tset(val):\n\t\tset_are_equal(val)\n\nvar _summary = null\nvar summary = null :\n\tget:\n\t\treturn get_summary()\n\tset(val):\n\t\tset_summary(val)\n\nvar _max_differences = 30\nvar max_differences = 30 :\n\tget:\n\t\treturn get_max_differences()\n\tset(val):\n\t\tset_max_differences(val)\n\nvar _differences = {}\nvar differences :\n\tget:\n\t\treturn get_differences()\n\tset(val):\n\t\tset_differences(val)\n\nfunc _block_set(which, val):\n\tpush_error(str('cannot set ', which, ', value [', val, '] ignored.'))\n\nfunc _to_string():\n\treturn str(get_summary()) # could be null, gotta str it.\n\nfunc get_are_equal():\n\treturn _are_equal\n\nfunc set_are_equal(r_eq):\n\t_are_equal = r_eq\n\nfunc get_summary():\n\treturn _summary\n\nfunc set_summary(smry):\n\t_summary = smry\n\nfunc get_total_count():\n\tpass\n\nfunc get_different_count():\n\tpass\n\nfunc get_short_summary():\n\treturn summary\n\nfunc get_max_differences():\n\treturn _max_differences\n\nfunc set_max_differences(max_diff):\n\t_max_differences = max_diff\n\nfunc get_differences():\n\treturn _differences\n\nfunc set_differences(diffs):\n\t_block_set('differences', diffs)\n\nfunc get_brackets():\n\treturn null\n"
  },
  {
    "path": "demo/addons/gut/compare_result.gd.uid",
    "content": "uid://cow1xqmqqvn4e\n"
  },
  {
    "path": "demo/addons/gut/diff_formatter.gd",
    "content": "var _strutils = GutUtils.Strutils.new()\nconst INDENT = '    '\nvar _max_to_display = 30\nconst ABSOLUTE_MAX_DISPLAYED = 10000\nconst UNLIMITED = -1\n\n\nfunc _single_diff(diff, depth=0):\n\tvar to_return = \"\"\n\tvar brackets = diff.get_brackets()\n\n\tif(brackets != null and !diff.are_equal):\n\t\tto_return = ''\n\t\tto_return += str(brackets.open, \"\\n\",\n\t\t\t_strutils.indent_text(differences_to_s(diff.differences, depth), depth+1, INDENT), \"\\n\",\n\t\t\tbrackets.close)\n\telse:\n\t\tto_return = str(diff)\n\n\treturn to_return\n\n\nfunc make_it(diff):\n\tvar to_return = ''\n\tif(diff.are_equal):\n\t\tto_return = diff.summary\n\telse:\n\t\tif(_max_to_display ==  ABSOLUTE_MAX_DISPLAYED):\n\t\t\tto_return = str(diff.get_value_1(), ' != ', diff.get_value_2())\n\t\telse:\n\t\t\tto_return = diff.get_short_summary()\n\t\tto_return +=  str(\"\\n\", _strutils.indent_text(_single_diff(diff, 0), 1, '  '))\n\treturn to_return\n\n\nfunc differences_to_s(differences, depth=0):\n\tvar to_return = ''\n\tvar keys = differences.keys()\n\tkeys.sort()\n\tvar limit = min(_max_to_display, differences.size())\n\n\tfor i in range(limit):\n\t\tvar key = keys[i]\n\t\tto_return += str(key, \":  \", _single_diff(differences[key], depth))\n\n\t\tif(i != limit -1):\n\t\t\tto_return += \"\\n\"\n\n\tif(differences.size() > _max_to_display):\n\t\tto_return += str(\"\\n\\n... \", differences.size() - _max_to_display, \" more.\")\n\n\treturn to_return\n\n\nfunc get_max_to_display():\n\treturn _max_to_display\n\n\nfunc set_max_to_display(max_to_display):\n\t_max_to_display = max_to_display\n\tif(_max_to_display == UNLIMITED):\n\t\t_max_to_display = ABSOLUTE_MAX_DISPLAYED\n"
  },
  {
    "path": "demo/addons/gut/diff_formatter.gd.uid",
    "content": "uid://ch2km05phxacd\n"
  },
  {
    "path": "demo/addons/gut/diff_tool.gd",
    "content": "extends 'res://addons/gut/compare_result.gd'\nconst INDENT = '    '\nenum {\n\tDEEP,\n\tSIMPLE\n}\n\nvar _strutils = GutUtils.Strutils.new()\nvar _compare = GutUtils.Comparator.new()\n\nvar _value_1 = null\nvar _value_2 = null\nvar _total_count = 0\nvar _diff_type = null\nvar _brackets = null\nvar _valid = true\nvar _desc_things = 'somethings'\n\n# -------- comapre_result.gd \"interface\" ---------------------\nfunc set_are_equal(val):\n\t_block_set('are_equal', val)\n\nfunc get_are_equal():\n\tif(!_valid):\n\t\treturn null\n\telse:\n\t\treturn differences.size() == 0\n\n\nfunc set_summary(val):\n\t_block_set('summary', val)\n\nfunc get_summary():\n\treturn summarize()\n\nfunc get_different_count():\n\treturn differences.size()\n\nfunc  get_total_count():\n\treturn _total_count\n\nfunc get_short_summary():\n\tvar text = str(_strutils.truncate_string(str(_value_1), 50),\n\t\t' ', _compare.get_compare_symbol(are_equal), ' ',\n\t\t_strutils.truncate_string(str(_value_2), 50))\n\tif(!are_equal):\n\t\ttext += str('  ', get_different_count(), ' of ', get_total_count(),\n\t\t\t' ', _desc_things, ' do not match.')\n\treturn text\n\nfunc get_brackets():\n\treturn _brackets\n# -------- comapre_result.gd \"interface\" ---------------------\n\n\nfunc _invalidate():\n\t_valid = false\n\tdifferences = null\n\n\nfunc _init(v1,v2,diff_type=DEEP):\n\t_value_1 = v1\n\t_value_2 = v2\n\t_diff_type = diff_type\n\t_compare.set_should_compare_int_to_float(false)\n\t_find_differences(_value_1, _value_2)\n\n\nfunc _find_differences(v1, v2):\n\tif(GutUtils.are_datatypes_same(v1, v2)):\n\t\tif(typeof(v1) == TYPE_ARRAY):\n\t\t\t_brackets = {'open':'[', 'close':']'}\n\t\t\t_desc_things = 'indexes'\n\t\t\t_diff_array(v1, v2)\n\t\telif(typeof(v2) == TYPE_DICTIONARY):\n\t\t\t_brackets = {'open':'{', 'close':'}'}\n\t\t\t_desc_things = 'keys'\n\t\t\t_diff_dictionary(v1, v2)\n\t\telse:\n\t\t\t_invalidate()\n\t\t\tGutUtils.get_logger().error('Only Arrays and Dictionaries are supported.')\n\telse:\n\t\t_invalidate()\n\t\tGutUtils.get_logger().error('Only Arrays and Dictionaries are supported.')\n\n\nfunc _diff_array(a1, a2):\n\t_total_count = max(a1.size(), a2.size())\n\tfor i in range(a1.size()):\n\t\tvar result = null\n\t\tif(i < a2.size()):\n\t\t\tif(_diff_type == DEEP):\n\t\t\t\tresult = _compare.deep(a1[i], a2[i])\n\t\t\telse:\n\t\t\t\tresult = _compare.simple(a1[i], a2[i])\n\t\telse:\n\t\t\tresult = _compare.simple(a1[i], _compare.MISSING, 'index')\n\n\t\tif(!result.are_equal):\n\t\t\tdifferences[i] = result\n\n\tif(a1.size() < a2.size()):\n\t\tfor i in range(a1.size(), a2.size()):\n\t\t\tdifferences[i] = _compare.simple(_compare.MISSING, a2[i], 'index')\n\n\nfunc _diff_dictionary(d1, d2):\n\tvar d1_keys = d1.keys()\n\tvar d2_keys = d2.keys()\n\n\t# Process all the keys in d1\n\t_total_count += d1_keys.size()\n\tfor key in d1_keys:\n\t\tif(!d2.has(key)):\n\t\t\tdifferences[key] = _compare.simple(d1[key], _compare.MISSING, 'key')\n\t\telse:\n\t\t\td2_keys.remove_at(d2_keys.find(key))\n\n\t\t\tvar result = null\n\t\t\tif(_diff_type == DEEP):\n\t\t\t\tresult = _compare.deep(d1[key], d2[key])\n\t\t\telse:\n\t\t\t\tresult = _compare.simple(d1[key], d2[key])\n\n\t\t\tif(!result.are_equal):\n\t\t\t\tdifferences[key] = result\n\n\t# Process all the keys in d2 that didn't exist in d1\n\t_total_count += d2_keys.size()\n\tfor i in range(d2_keys.size()):\n\t\tdifferences[d2_keys[i]] = _compare.simple(_compare.MISSING, d2[d2_keys[i]], 'key')\n\n\nfunc summarize():\n\tvar summary = ''\n\n\tif(are_equal):\n\t\tsummary = get_short_summary()\n\telse:\n\t\tvar formatter = load('res://addons/gut/diff_formatter.gd').new()\n\t\tformatter.set_max_to_display(max_differences)\n\t\tsummary = formatter.make_it(self)\n\n\treturn summary\n\n\nfunc get_diff_type():\n\treturn _diff_type\n\n\nfunc get_value_1():\n\treturn _value_1\n\n\nfunc get_value_2():\n\treturn _value_2\n"
  },
  {
    "path": "demo/addons/gut/diff_tool.gd.uid",
    "content": "uid://beoxokvl1hjs8\n"
  },
  {
    "path": "demo/addons/gut/double_templates/function_template.txt",
    "content": "{func_decleration}\n\tif(__gutdbl == null):\n\t\treturn\n\n\t__gutdbl.spy_on('{method_name}', {param_array})\n\tif(__gutdbl.is_stubbed_to_call_super('{method_name}', {param_array})):\n\t\treturn {super_call}\n\telse:\n\t\treturn await __gutdbl.handle_other_stubs('{method_name}', {param_array})\n"
  },
  {
    "path": "demo/addons/gut/double_templates/init_template.txt",
    "content": "{func_decleration}:\n\tsuper({super_params})\n\t__gutdbl.spy_on('{method_name}', {param_array})\n\n"
  },
  {
    "path": "demo/addons/gut/double_templates/script_template.txt",
    "content": "# ##############################################################################\n# Gut Doubled Script\n# ##############################################################################\n{extends}\n\n{constants}\n\n{properties}\n\n# ------------------------------------------------------------------------------\n# GUT stuff\n# ------------------------------------------------------------------------------\nvar __gutdbl_values = {\n\tthepath = '{path}',\n\tsubpath = '{subpath}',\n\tstubber = {stubber_id},\n\tspy = {spy_id},\n\tgut = {gut_id},\n\tfrom_singleton = '{singleton_name}',\n\tis_partial = {is_partial},\n\tdoubled_methods = {doubled_methods},\n}\nvar __gutdbl = load('res://addons/gut/double_tools.gd').new(self)\n\n# Here so other things can check for a method to know if this is a double.\nfunc __gutdbl_check_method__():\n\tpass\n\n# Cleanup called by GUT after tests have finished.  Important for RefCounted\n# objects.  Nodes are freed, and won't have this method called on them.\nfunc __gutdbl_done():\n\t__gutdbl = null\n\t__gutdbl_values.clear()\n\n# ------------------------------------------------------------------------------\n# Doubled Methods\n# ------------------------------------------------------------------------------\n"
  },
  {
    "path": "demo/addons/gut/double_tools.gd",
    "content": "var thepath = ''\nvar subpath = ''\nvar from_singleton = null\nvar is_partial = null\n\nvar double_ref : WeakRef = null\nvar stubber_ref : WeakRef = null\nvar spy_ref : WeakRef = null\nvar gut_ref : WeakRef = null\n\nconst NO_DEFAULT_VALUE = '!__gut__no__default__value__!'\nfunc _init(double = null):\n\tif(double != null):\n\t\tvar values = double.__gutdbl_values\n\t\tdouble_ref = weakref(double)\n\t\tthepath = values.thepath\n\t\tsubpath = values.subpath\n\t\tstubber_ref = weakref_from_id(values.stubber)\n\t\tspy_ref = weakref_from_id(values.spy)\n\t\tgut_ref = weakref_from_id(values.gut)\n\t\tfrom_singleton = values.from_singleton\n\t\tis_partial = values.is_partial\n\n\t\tif(gut_ref.get_ref() != null):\n\t\t\tgut_ref.get_ref().get_autofree().add_free(double_ref.get_ref())\n\n\nfunc _get_stubbed_method_to_call(method_name, called_with):\n\tvar method = stubber_ref.get_ref().get_call_this(double_ref.get_ref(), method_name, called_with)\n\tif(method != null):\n\t\tmethod = method.bindv(called_with)\n\t\treturn method\n\treturn method\n\n\nfunc weakref_from_id(inst_id):\n\tif(inst_id ==  -1):\n\t\treturn weakref(null)\n\telse:\n\t\treturn weakref(instance_from_id(inst_id))\n\n\nfunc is_stubbed_to_call_super(method_name, called_with):\n\tif(stubber_ref.get_ref() != null):\n\t\treturn stubber_ref.get_ref().should_call_super(double_ref.get_ref(), method_name, called_with)\n\telse:\n\t\treturn false\n\n\nfunc handle_other_stubs(method_name, called_with):\n\tif(stubber_ref.get_ref() == null):\n\t\treturn\n\n\tvar method = _get_stubbed_method_to_call(method_name, called_with)\n\tif(method != null):\n\t\treturn await method.call()\n\telse:\n\t\treturn stubber_ref.get_ref().get_return(double_ref.get_ref(), method_name, called_with)\n\n\nfunc spy_on(method_name, called_with):\n\tif(spy_ref.get_ref() != null):\n\t\tspy_ref.get_ref().add_call(double_ref.get_ref(), method_name, called_with)\n\n\nfunc default_val(method_name, p_index):\n\tif(stubber_ref.get_ref() == null):\n\t\treturn null\n\telse:\n\t\treturn stubber_ref.get_ref().get_default_value(double_ref.get_ref(), method_name, p_index)\n"
  },
  {
    "path": "demo/addons/gut/double_tools.gd.uid",
    "content": "uid://tr4khoco1hef\n"
  },
  {
    "path": "demo/addons/gut/doubler.gd",
    "content": "extends RefCounted\n\n\nvar _base_script_text = GutUtils.get_file_as_text('res://addons/gut/double_templates/script_template.txt')\nvar _script_collector = GutUtils.ScriptCollector.new()\n# used by tests for debugging purposes.\nvar print_source = false\nvar inner_class_registry = GutUtils.InnerClassRegistry.new()\n\n# ###############\n# Properties\n# ###############\nvar _stubber = GutUtils.Stubber.new()\nfunc get_stubber():\n\treturn _stubber\nfunc set_stubber(stubber):\n\t_stubber = stubber\n\nvar _lgr = GutUtils.get_logger()\nfunc get_logger():\n\treturn _lgr\nfunc set_logger(logger):\n\t_lgr = logger\n\t_method_maker.set_logger(logger)\n\nvar _spy = null\nfunc get_spy():\n\treturn _spy\nfunc set_spy(spy):\n\t_spy = spy\n\nvar _gut = null\nfunc get_gut():\n\treturn _gut\nfunc set_gut(gut):\n\t_gut = gut\n\nvar _strategy = null\nfunc get_strategy():\n\treturn _strategy\nfunc set_strategy(strategy):\n\tif(GutUtils.DOUBLE_STRATEGY.values().has(strategy)):\n\t\t_strategy = strategy\n\telse:\n\t\t_lgr.error(str('doubler.gd:  invalid double strategy ', strategy))\n\n\nvar _method_maker = GutUtils.MethodMaker.new()\nfunc get_method_maker():\n\treturn _method_maker\n\nvar _ignored_methods = GutUtils.OneToMany.new()\nfunc get_ignored_methods():\n\treturn _ignored_methods\n\n# ###############\n# Private\n# ###############\nfunc _init(strategy=GutUtils.DOUBLE_STRATEGY.SCRIPT_ONLY):\n\tset_logger(GutUtils.get_logger())\n\t_strategy = strategy\n\n\nfunc _get_indented_line(indents, text):\n\tvar to_return = ''\n\tfor _i in range(indents):\n\t\tto_return += \"\\t\"\n\treturn str(to_return, text, \"\\n\")\n\n\nfunc _stub_to_call_super(parsed, method_name):\n\tif(!parsed.get_method(method_name).is_eligible_for_doubling()):\n\t\treturn\n\n\tvar params = GutUtils.StubParams.new(parsed.script_path, method_name, parsed.subpath)\n\tparams.to_call_super()\n\t_stubber.add_stub(params)\n\n\nfunc _get_base_script_text(parsed, override_path, partial, included_methods):\n\tvar path = parsed.script_path\n\tif(override_path != null):\n\t\tpath = override_path\n\n\tvar stubber_id = -1\n\tif(_stubber != null):\n\t\tstubber_id = _stubber.get_instance_id()\n\n\tvar spy_id = -1\n\tif(_spy != null):\n\t\tspy_id = _spy.get_instance_id()\n\n\tvar gut_id = -1\n\tif(_gut != null):\n\t\tgut_id = _gut.get_instance_id()\n\n\tvar extends_text  = parsed.get_extends_text()\n\n\tvar values = {\n\t\t# Top  sections\n\t\t\"extends\":extends_text,\n\t\t\"constants\":'',#obj_info.get_constants_text(),\n\t\t\"properties\":'',#obj_info.get_properties_text(),\n\n\t\t# metadata values\n\t\t\"path\":path,\n\t\t\"subpath\":GutUtils.nvl(parsed.subpath, ''),\n\t\t\"stubber_id\":stubber_id,\n\t\t\"spy_id\":spy_id,\n\t\t\"gut_id\":gut_id,\n\t\t\"singleton_name\":'',#GutUtils.nvl(obj_info.get_singleton_name(), ''),\n\t\t\"is_partial\":partial,\n\t\t\"doubled_methods\":included_methods,\n\t}\n\n\treturn _base_script_text.format(values)\n\n\nfunc _is_method_eligible_for_doubling(parsed_script, parsed_method):\n\treturn !parsed_method.is_accessor() and \\\n\t\tparsed_method.is_eligible_for_doubling() and \\\n\t\t!_ignored_methods.has(parsed_script.resource, parsed_method.meta.name)\n\n\n# Disable the native_method_override setting so that doubles do not generate\n# errors or warnings when doubling with INCLUDE_NATIVE or when a method has\n# been added because of param_count stub.\nfunc _create_script_no_warnings(src):\n\tvar prev_native_override_value = null\n\tvar native_method_override = 'debug/gdscript/warnings/native_method_override'\n\tprev_native_override_value = ProjectSettings.get_setting(native_method_override)\n\tProjectSettings.set_setting(native_method_override, 0)\n\n\tvar DblClass = GutUtils.create_script_from_source(src)\n\n\tProjectSettings.set_setting(native_method_override, prev_native_override_value)\n\treturn DblClass\n\n\nfunc _create_double(parsed, strategy, override_path, partial):\n\tvar dbl_src = \"\"\n\tvar included_methods = []\n\n\tfor method in parsed.get_local_methods():\n\t\tif(_is_method_eligible_for_doubling(parsed, method)):\n\t\t\tincluded_methods.append(method.meta.name)\n\t\t\tdbl_src += _get_func_text(method.meta)\n\n\tif(strategy == GutUtils.DOUBLE_STRATEGY.INCLUDE_NATIVE):\n\t\tfor method in parsed.get_super_methods():\n\t\t\tif(_is_method_eligible_for_doubling(parsed, method)):\n\t\t\t\tincluded_methods.append(method.meta.name)\n\t\t\t\t_stub_to_call_super(parsed, method.meta.name)\n\t\t\t\tdbl_src += _get_func_text(method.meta)\n\n\tvar base_script = _get_base_script_text(parsed, override_path, partial, included_methods)\n\tdbl_src = base_script + \"\\n\\n\" + dbl_src\n\n\tif(print_source):\n\t\tvar to_print :String = GutUtils.add_line_numbers(dbl_src)\n\t\tto_print = to_print.rstrip(\"\\n\")\n\t\t_lgr.log(str(to_print))\n\n\tvar DblClass = _create_script_no_warnings(dbl_src)\n\tif(_stubber != null):\n\t\t_stub_method_default_values(DblClass, parsed, strategy)\n\n\tif(print_source):\n\t\t_lgr.log(str(\"  path | \", DblClass.resource_path, \"\\n\"))\n\n\treturn DblClass\n\n\nfunc _stub_method_default_values(which, parsed, strategy):\n\tfor method in parsed.get_local_methods():\n\t\tif(method.is_eligible_for_doubling() and !_ignored_methods.has(parsed.resource, method.meta.name)):\n\t\t\t_stubber.stub_defaults_from_meta(parsed.script_path, method.meta)\n\n\nfunc _double_scene_and_script(scene, strategy, partial):\n\tvar dbl_bundle = scene._bundled.duplicate(true)\n\tvar script_obj = GutUtils.get_scene_script_object(scene)\n\t# I'm not sure if the script object for the root node of a packed scene is\n\t# always the first entry in \"variants\" so this tries to find it.\n\tvar script_index = dbl_bundle[\"variants\"].find(script_obj)\n\tvar script_dbl = null\n\n\tif(script_obj != null):\n\t\tif(partial):\n\t\t\tscript_dbl = _partial_double(script_obj, strategy, scene.get_path())\n\t\telse:\n\t\t\tscript_dbl = _double(script_obj, strategy, scene.get_path())\n\n\tif(script_index != -1):\n\t\tdbl_bundle[\"variants\"][script_index] = script_dbl\n\n\tvar doubled_scene = PackedScene.new()\n\tdoubled_scene._set_bundled_scene(dbl_bundle)\n\n\treturn doubled_scene\n\n\nfunc _get_inst_id_ref_str(inst):\n\tvar ref_str = 'null'\n\tif(inst):\n\t\tref_str = str('instance_from_id(', inst.get_instance_id(),')')\n\treturn ref_str\n\n\nfunc _get_func_text(method_hash):\n\treturn _method_maker.get_function_text(method_hash) + \"\\n\"\n\n\nfunc _parse_script(obj):\n\tvar parsed = null\n\n\tif(GutUtils.is_inner_class(obj)):\n\t\tif(inner_class_registry.has(obj)):\n\t\t\tparsed = _script_collector.parse(inner_class_registry.get_base_resource(obj), obj)\n\t\telse:\n\t\t\t_lgr.error('Doubling Inner Classes requires you register them first.  Call register_inner_classes passing the script that contains the inner class.')\n\telse:\n\t\tparsed = _script_collector.parse(obj)\n\n\treturn parsed\n\n\n# Override path is used with scenes.\nfunc _double(obj, strategy, override_path=null):\n\tvar parsed = _parse_script(obj)\n\tif(parsed != null):\n\t\treturn _create_double(parsed, strategy, override_path, false)\n\n\nfunc _partial_double(obj, strategy, override_path=null):\n\tvar parsed = _parse_script(obj)\n\tif(parsed != null):\n\t\treturn _create_double(parsed, strategy, override_path, true)\n\n\n# -------------------------\n# Public\n# -------------------------\n\n# double a script/object\nfunc double(obj, strategy=_strategy):\n\treturn _double(obj, strategy)\n\nfunc partial_double(obj, strategy=_strategy):\n\treturn _partial_double(obj, strategy)\n\n\n# double a scene\nfunc double_scene(scene, strategy=_strategy):\n\treturn _double_scene_and_script(scene, strategy, false)\n\n\nfunc partial_double_scene(scene, strategy=_strategy):\n\treturn _double_scene_and_script(scene, strategy, true)\n\n\nfunc double_gdnative(which):\n\treturn _double(which, GutUtils.DOUBLE_STRATEGY.INCLUDE_NATIVE)\n\n\nfunc partial_double_gdnative(which):\n\treturn _partial_double(which, GutUtils.DOUBLE_STRATEGY.INCLUDE_NATIVE)\n\n\nfunc double_inner(parent, inner, strategy=_strategy):\n\tvar parsed = _script_collector.parse(parent, inner)\n\treturn _create_double(parsed, strategy, null, false)\n\n\nfunc partial_double_inner(parent, inner, strategy=_strategy):\n\tvar parsed = _script_collector.parse(parent, inner)\n\treturn _create_double(parsed, strategy, null, true)\n\n\nfunc add_ignored_method(obj, method_name):\n\t_ignored_methods.add(obj, method_name)\n\n\n\n# ##############################################################################\n#(G)odot (U)nit (T)est class\n#\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n"
  },
  {
    "path": "demo/addons/gut/doubler.gd.uid",
    "content": "uid://cpy013l0wqwmg\n"
  },
  {
    "path": "demo/addons/gut/dynamic_gdscript.gd",
    "content": "@tool\nvar default_script_name_no_extension = 'gut_dynamic_script'\nvar default_script_resource_path = 'res://addons/gut/not_a_real_file/'\nvar default_script_extension = \"gd\"\n\nvar _created_script_count = 0\n\n\n# Creates a loaded script from the passed in source.  This loaded script is\n# returned unless there is an error.  When an error occcurs the error number\n# is returned instead.\nfunc create_script_from_source(source, override_path=null):\n\t_created_script_count += 1\n\tvar r_path = str(default_script_resource_path,\n\t\tdefault_script_name_no_extension, '_', _created_script_count, \".\",\n\t\tdefault_script_extension)\n\n\tif(override_path != null):\n\t\tr_path = override_path\n\n\tvar DynamicScript = GDScript.new()\n\tDynamicScript.source_code = source.dedent()\n\t# The resource_path must be unique or Godot thinks it is trying\n\t# to load something it has already loaded and generates an error like\n\t# ERROR: Another resource is loaded from path 'workaround for godot\n\t# issue #65263' (possible cyclic resource inclusion).\n\tDynamicScript.resource_path = r_path\n\tvar result = DynamicScript.reload()\n\tif(result != OK):\n\t\tDynamicScript = result\n\n\treturn DynamicScript\n\n"
  },
  {
    "path": "demo/addons/gut/dynamic_gdscript.gd.uid",
    "content": "uid://cnbjsrik0p5uf\n"
  },
  {
    "path": "demo/addons/gut/editor_caret_context_notifier.gd",
    "content": "@tool\nextends Node\n# ##############################################################################\n#\n# Watches script editors and emits a signal whenever the method, inner class,\n# or script changes based on cursor position and other stuff.\n#\n# Basically, whenever this thing's signal is emitted, then the RunAtCursor\n# buttons should be updated to match the data passed to the signal.\n# ##############################################################################\n# In the editor, whenever a script is opened you get these new things that\n# hang off of EditorInterface.get_script_editor()\n# \t* ScriptEditorBase\n#\t\t* CodeEdit\n# ##############################################################################\n\n\nvar _last_info : Dictionary = {}\nvar _last_line = -1\n# This is the control that holds all the individual editors.\nvar _current_script_editor : ScriptEditor = null\n# Reference to the GDScript for the last script we were notified about.\nvar _current_script = null\nvar _current_script_is_test_script = false\nvar _current_editor_base : ScriptEditorBase = null\nvar _current_editor : CodeEdit = null\n# Quick lookup of editors based on the current script.\nvar _editors_for_scripts : Dictionary= {}\n\n\n# In order to keep the data that comes back from the emitted signal way more\n# usable, we have to know what GUT looks for for an inner-test-class prefix.\n# If we didn't do this, then this thing would have to return all the inner\n# classes and then we'd have to determine if we were in an inner-test-class\n# outside of here by traversing all the classes returned.  It makes this thing\n# less generic and know too much, but this is probably already too generic as\n# it is.\nvar inner_class_prefix = \"Test\"\nvar method_prefix = \"test_\"\nvar script_prefix = \"test_\"\nvar script_suffix = \".gd\"\n\n\n# Based on cursor and open editors, this will be emitted.  You do what you\n# want with it.\nsignal it_changed(change_data)\n\n\nfunc _ready():\n\t# This will not change, and should not change, over the course of a session.\n\t_current_script_editor = EditorInterface.get_script_editor()\n\t_current_script_editor.editor_script_changed.connect(_on_editor_script_changed)\n\t_current_script_editor.script_close.connect(_on_editor_script_close)\n\n\nfunc _handle_caret_location(which):\n\tvar current_line = which.get_caret_line(0) + 1\n\tif(_last_line != current_line):\n\t\t_last_line = current_line\n\n\t\tif(_current_script_is_test_script):\n\t\t\tvar new_info = _make_info(which, _current_script, _current_script_is_test_script)\n\t\t\tif(_last_info != new_info):\n\t\t\t\t_last_info = new_info\n\t\t\t\tit_changed.emit(_last_info.duplicate())\n\n\nfunc _get_func_name_from_line(text):\n\ttext = text.strip_edges()\n\tvar left = text.split(\"(\")[0]\n\tvar func_name = left.split(\" \")[1]\n\treturn func_name\n\n\nfunc _get_class_name_from_line(text):\n\ttext = text.strip_edges()\n\tvar right = text.split(\" \")[1]\n\tvar the_name = right.rstrip(\":\")\n\treturn the_name\n\n\nfunc _make_info(editor, script, test_script_flag):\n\tif(editor == null):\n\t\treturn\n\n\tvar info = {\n\t\tscript = script,\n\t\tinner_class = null,\n\t\tmethod = null,\n\t\tis_test_script = test_script_flag\n\t}\n\n\tvar start_line = editor.get_caret_line()\n\tvar line = start_line\n\tvar done_func = false\n\tvar done_inner = false\n\twhile(line > 0 and (!done_func or !done_inner)):\n\t\tif(editor.can_fold_line(line)):\n\t\t\tvar text = editor.get_line(line)\n\t\t\tvar strip_text = text.strip_edges(true, false) # only left\n\n\t\t\tif(!done_func and strip_text.begins_with(\"func \")):\n\t\t\t\tinfo.method = _get_func_name_from_line(text)\n\t\t\t\tdone_func = true\n\t\t\t\t# If the func line is left justified then there won't be any\n\t\t\t\t# inner classes above it.\n\t\t\t\tif(editor.get_indent_level(line) == 0):\n\t\t\t\t\tdone_inner = true\n\n\t\t\tif(!done_inner and strip_text.begins_with(\"class\")):\n\t\t\t\tvar inner_name = _get_class_name_from_line(text)\n\t\t\t\t# See note about inner_class_prefix, this knows too much, but\n\t\t\t\t# if it was to know less it would insanely more difficult\n\t\t\t\t# everywhere.\n\t\t\t\tif(inner_name.begins_with(inner_class_prefix)):\n\t\t\t\t\tinfo.inner_class = inner_name\n\t\t\t\t\tdone_inner = true\n\t\t\t\t\tdone_func = true\n\t\tline -= 1\n\n\t# print('parsed lines:  ', start_line - line, '(', info.inner_class, ':', info.method, ')')\n\treturn info\n# -------------\n# Events\n# -------------\n\n# Fired whenever the script changes.  This does not fire if you select something\n# other than a script from the tree.  So if you click a help file and then\n# back to the same file, then this will fire for the same script\n#\n# This can fire multiple times for the same script when a script is opened.\nfunc _on_editor_script_changed(script):\n\t_last_line = -1\n\t_current_script = script\n\t_current_editor_base = _current_script_editor.get_current_editor()\n\tif(_current_editor_base.get_base_editor() is CodeEdit):\n\t\t_current_editor = _current_editor_base.get_base_editor()\n\t\tif(!_current_editor.caret_changed.is_connected(_on_caret_changed)):\n\t\t\t_current_editor.caret_changed.connect(_on_caret_changed.bind(_current_editor))\n\telse:\n\t\t_current_editor = null\n\t_editors_for_scripts[script] = _current_editor\n\t_current_script_is_test_script = is_test_script(_current_script)\n\n\t_handle_caret_location(_current_editor)\n\n\nfunc _on_editor_script_close(script):\n\tvar script_editor = _editors_for_scripts.get(script, null)\n\tif(script_editor != null):\n\t\tif(script_editor.caret_changed.is_connected(_on_caret_changed)):\n\t\t\tscript_editor.caret_changed.disconnect(_on_caret_changed)\n\t\t\t_editors_for_scripts.erase(script)\n\n\nfunc _on_caret_changed(which):\n\t# Sometimes this is fired for editors that are not the current.  I could\n\t# make this fire by saving a file in an external editor.  I was unable to\n\t# get useful data out when it wasn't the current editor so I'm only doing\n\t# anything when it is the current editor.\n\tif(which == _current_editor):\n\t\t_handle_caret_location(which)\n\n\nfunc _could_be_test_script(script):\n\treturn \tscript.resource_path.get_file().begins_with(script_prefix) and \\\n\t\tscript.resource_path.get_file().ends_with(script_suffix)\n\n# -------------\n# Public\n# -------------\nvar _scripts_that_have_been_warned_about = []\nvar _we_have_warned_enough = false\nvar _max_warnings = 5\nfunc is_test_script(script):\n\tvar base = script.get_base_script()\n\tif(base == null and script.get_script_method_list().size() == 0 and _could_be_test_script(script)):\n\t\tif(OS.is_stdout_verbose() or (!_scripts_that_have_been_warned_about.has(script.resource_path) and !_we_have_warned_enough)):\n\t\t\t_scripts_that_have_been_warned_about.append(script.resource_path)\n\t\t\tpush_warning(str('[GUT] Treating ', script.resource_path, \" as test script:  \",\n\t\t\t\t\"GUT was not able to retrieve information about this script.  If this is \",\n\t\t\t\t\"a new script you can ignore this warning.  Otherwise, this may \",\n\t\t\t\t\"have to do with having VSCode open.  Restarting Godot sometimes helps.  See \",\n\t\t\t\t\"https://github.com/bitwes/Gut/issues/754\"))\n\t\t\tif(!OS.is_stdout_verbose() and _scripts_that_have_been_warned_about.size() >= _max_warnings):\n\t\t\t\tprint(\"[GUT] Disabling warning.\")\n\t\t\t\t_we_have_warned_enough = true\n\n\t\t# We can't know if this is a test script.  It's more usable if we\n\t\t# assume this is a test script.\n\t\treturn true\n\telse:\n\t\twhile(base and base.resource_path != 'res://addons/gut/test.gd'):\n\t\t\tbase = base.get_base_script()\n\t\treturn base != null\n\n\nfunc get_info():\n\treturn _last_info.duplicate()\n\n\nfunc log_values():\n\tprint(\"---------------------------------------------------------------\")\n\tprint(\"script                   \", _current_script)\n\tprint(\"script_editor            \", _current_script_editor)\n\tprint(\"editor_base              \", _current_editor_base)\n\tprint(\"editor                   \", _current_editor)\n"
  },
  {
    "path": "demo/addons/gut/editor_caret_context_notifier.gd.uid",
    "content": "uid://c110s7a32x4su\n"
  },
  {
    "path": "demo/addons/gut/error_tracker.gd",
    "content": "extends Logger\nclass_name GutErrorTracker\n\n# ------------------------------------------------------------------------------\n# Static methods wrap around add/remove logger to make disabling the logger\n# easier and to help avoid misusing add/remove in tests.  If GUT needs to\n# add/remove a logger then this is how it should do it.\n# ------------------------------------------------------------------------------\nstatic var registered_loggers := {}\nstatic var register_loggers = true\n\nstatic func register_logger(which):\n\tif(register_loggers and !registered_loggers.has(which)):\n\t\tOS.add_logger(which)\n\t\tregistered_loggers[which] = get_stack()\n\n\nstatic func deregister_logger(which):\n\tif(registered_loggers.has(which)):\n\t\tOS.remove_logger(which)\n\t\tregistered_loggers.erase(which)\n\n\n\n\n# ------------------------------------------------------------------------------\n# GutErrorTracker\n# ------------------------------------------------------------------------------\nvar _current_test_id = GutUtils.NO_TEST\nvar _mutex = Mutex.new()\n\nvar errors = GutUtils.OneToMany.new()\n\nvar treat_gut_errors_as : GutUtils.TREAT_AS = GutUtils.TREAT_AS.FAILURE\nvar treat_engine_errors_as : GutUtils.TREAT_AS = GutUtils.TREAT_AS.FAILURE\nvar treat_push_error_as : GutUtils.TREAT_AS = GutUtils.TREAT_AS.FAILURE\nvar disabled = false\n\n\n# ----------------\n#region Private\n# ----------------\n\nfunc _get_stack_data(current_test_name):\n\tvar test_entry = {}\n\tvar stackTrace = get_stack()\n\n\tif(stackTrace!=null):\n\t\tvar index = 0\n\t\twhile(index < stackTrace.size() and test_entry == {}):\n\t\t\tvar line = stackTrace[index]\n\t\t\tvar function = line.get(\"function\")\n\t\t\tif function == current_test_name:\n\t\t\t\ttest_entry = stackTrace[index]\n\t\t\telse:\n\t\t\t\tindex += 1\n\n\t\tfor i in range(index):\n\t\t\tstackTrace.remove_at(0)\n\n\treturn {\n\t\t\"test_entry\" = test_entry,\n\t\t\"full_stack\" = stackTrace\n\t}\n\n\nfunc _is_error_failable(error : GutTrackedError):\n\tvar is_it = false\n\tif(error.handled == false):\n\t\tif(error.is_gut_error()):\n\t\t\tis_it = treat_gut_errors_as == GutUtils.TREAT_AS.FAILURE\n\t\telif(error.is_push_error()):\n\t\t\tis_it = treat_push_error_as == GutUtils.TREAT_AS.FAILURE\n\t\telif(error.is_engine_error()):\n\t\t\tis_it = treat_engine_errors_as == GutUtils.TREAT_AS.FAILURE\n\treturn is_it\n\n# ----------------\n#endregion\n#region Godot's Logger Overrides\n# ----------------\n\n# Godot's Logger virtual method for errors\nfunc _log_error(function: String, file: String, line: int,\n\tcode: String, rationale: String, editor_notify: bool,\n\terror_type: int, script_backtraces: Array[ScriptBacktrace]) -> void:\n\n\t\tadd_error(function, file, line,\n\t\t\tcode, rationale, editor_notify,\n\t\t\terror_type, script_backtraces)\n\n# Godot's Logger virtual method for any output?\n# func _log_message(message: String, error: bool) -> void:\n# \tpass\n\n# ----------------\n#endregion\n#region Public\n# ----------------\n\nfunc start_test(test_id):\n\t_current_test_id = test_id\n\n\nfunc end_test():\n\t_current_test_id = GutUtils.NO_TEST\n\n\nfunc did_test_error(test_id=_current_test_id):\n\treturn errors.size(test_id) > 0\n\n\nfunc get_current_test_errors():\n\treturn errors.items.get(_current_test_id, [])\n\n\n# This should look through all the errors for a test and see if a failure\n# should happen based off of flags.\nfunc should_test_fail_from_errors(test_id = _current_test_id):\n\tvar to_return = false\n\tif(errors.items.has(test_id)):\n\t\tvar errs = errors.items[test_id]\n\t\tvar index = 0\n\t\twhile(index < errs.size() and !to_return):\n\t\t\tvar error = errs[index]\n\t\t\tto_return = _is_error_failable(error)\n\t\t\tindex += 1\n\treturn to_return\n\n\nfunc get_errors_for_test(test_id=_current_test_id):\n\tvar to_return = []\n\tif(errors.items.has(test_id)):\n\t\tto_return = errors.items[test_id].duplicate()\n\n\treturn to_return\n\n\n# Returns emtpy string or text for errors that occurred during the test that\n# should cause failure based on this class' flags.\nfunc get_fail_text_for_errors(test_id=_current_test_id) -> String:\n\tvar error_texts = []\n\n\tif(errors.items.has(test_id)):\n\t\tfor error in errors.items[test_id]:\n\t\t\tif(_is_error_failable(error)):\n\t\t\t\terror_texts.append(str('<', error.get_error_type_name(), '>', error.code))\n\n\tvar to_return = \"\"\n\tfor i in error_texts.size():\n\t\tif(to_return != \"\"):\n\t\t\tto_return += \"\\n\"\n\t\tto_return += str(\"[\", i + 1, \"] \", error_texts[i])\n\n\treturn to_return\n\n\nfunc add_gut_error(text) -> GutTrackedError:\n\tif(_current_test_id != GutUtils.NO_TEST):\n\t\tvar data = _get_stack_data(_current_test_id)\n\t\tif(data.test_entry != {}):\n\t\t\treturn add_error(_current_test_id, data.test_entry.source, data.test_entry.line,\n\t\t\t\ttext, '', false,\n\t\t\t\tGutUtils.GUT_ERROR_TYPE, data.full_stack)\n\n\treturn add_error(_current_test_id, \"unknown\", -1,\n\t\ttext, '', false,\n\t\tGutUtils.GUT_ERROR_TYPE, get_stack())\n\n\nfunc add_error(function: String, file: String, line: int,\n\tcode: String, rationale: String, editor_notify: bool,\n\terror_type: int, script_backtraces: Array) -> GutTrackedError:\n\t\tif(disabled):\n\t\t\treturn\n\n\t\t_mutex.lock()\n\n\t\tvar err := GutTrackedError.new()\n\t\terr.backtrace = script_backtraces\n\t\terr.code = code\n\t\terr.rationale = rationale\n\t\terr.error_type = error_type\n\t\terr.editor_notify = editor_notify\n\t\terr.file = file\n\t\terr.function = function\n\t\terr.line = line\n\n\t\terrors.add(_current_test_id, err)\n\n\t\t_mutex.unlock()\n\n\t\treturn err\n"
  },
  {
    "path": "demo/addons/gut/error_tracker.gd.uid",
    "content": "uid://35kxgqotjmlu\n"
  },
  {
    "path": "demo/addons/gut/fonts/OFL.txt",
    "content": "Copyright (c) 2009, Mark Simonson (http://www.ms-studio.com, mark@marksimonson.com),\nwith Reserved Font Name Anonymous Pro.\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at:\nhttp://scripts.sil.org/OFL\n\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe goals of the Open Font License (OFL) are to stimulate worldwide\ndevelopment of collaborative font projects, to support the font creation\nefforts of academic and linguistic communities, and to provide a free and\nopen framework in which fonts may be shared and improved in partnership\nwith others.\n\nThe OFL allows the licensed fonts to be used, studied, modified and\nredistributed freely as long as they are not sold by themselves. The\nfonts, including any derivative works, can be bundled, embedded,\nredistributed and/or sold with any software provided that any reserved\nnames are not used by derivative works. The fonts and derivatives,\nhowever, cannot be released under any other type of license. The\nrequirement for fonts to remain under this license does not apply\nto any document created using the fonts or their derivatives.\n\nDEFINITIONS\n\"Font Software\" refers to the set of files released by the Copyright\nHolder(s) under this license and clearly marked as such. This may\ninclude source files, build scripts and documentation.\n\n\"Reserved Font Name\" refers to any names specified as such after the\ncopyright statement(s).\n\n\"Original Version\" refers to the collection of Font Software components as\ndistributed by the Copyright Holder(s).\n\n\"Modified Version\" refers to any derivative made by adding to, deleting,\nor substituting -- in part or in whole -- any of the components of the\nOriginal Version, by changing formats or by porting the Font Software to a\nnew environment.\n\n\"Author\" refers to any designer, engineer, programmer, technical\nwriter or other person who contributed to the Font Software.\n\nPERMISSION & CONDITIONS\nPermission is hereby granted, free of charge, to any person obtaining\na copy of the Font Software, to use, study, copy, merge, embed, modify,\nredistribute, and sell modified and unmodified copies of the Font\nSoftware, subject to the following conditions:\n\n1) Neither the Font Software nor any of its individual components,\nin Original or Modified Versions, may be sold by itself.\n\n2) Original or Modified Versions of the Font Software may be bundled,\nredistributed and/or sold with any software, provided that each copy\ncontains the above copyright notice and this license. These can be\nincluded either as stand-alone text files, human-readable headers or\nin the appropriate machine-readable metadata fields within text or\nbinary files as long as those fields can be easily viewed by the user.\n\n3) No Modified Version of the Font Software may use the Reserved Font\nName(s) unless explicit written permission is granted by the corresponding\nCopyright Holder. This restriction only applies to the primary font name as\npresented to the users.\n\n4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\nSoftware shall not be used to promote, endorse or advertise any\nModified Version, except to acknowledge the contribution(s) of the\nCopyright Holder(s) and the Author(s) or with their explicit written\npermission.\n\n5) The Font Software, modified or unmodified, in part or in whole,\nmust be distributed entirely under this license, and must not be\ndistributed under any other license. The requirement for fonts to\nremain under this license does not apply to any document created\nusing the Font Software.\n\nTERMINATION\nThis license becomes null and void if any of the above conditions are\nnot met.\n\nDISCLAIMER\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\nOTHER DEALINGS IN THE FONT SOFTWARE.\n"
  },
  {
    "path": "demo/addons/gut/gui/EditorRadioButton.tres",
    "content": "[gd_resource type=\"Theme\" load_steps=3 format=3 uid=\"uid://dssgvu257o1si\"]\n\n[sub_resource type=\"StyleBoxFlat\" id=\"StyleBoxFlat_u716c\"]\nbg_color = Color(0.43137255, 0.8784314, 0.6156863, 0.5254902)\n\n[sub_resource type=\"StyleBoxFlat\" id=\"StyleBoxFlat_ht2pf\"]\nbg_color = Color(0, 0.44705883, 0.23921569, 1)\n\n[resource]\nButton/colors/font_hover_pressed_color = Color(1, 1, 1, 1)\nButton/colors/font_pressed_color = Color(1, 1, 1, 1)\nButton/styles/hover = SubResource(\"StyleBoxFlat_u716c\")\nButton/styles/pressed = SubResource(\"StyleBoxFlat_ht2pf\")\n"
  },
  {
    "path": "demo/addons/gut/gui/GutBottomPanel.gd",
    "content": "@tool\nextends Control\n\nvar GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')\nvar GutConfigGui = load('res://addons/gut/gui/gut_config_gui.gd')\nvar AboutWindow = load(\"res://addons/gut/gui/about.tscn\")\n\nvar _interface = null;\nvar _is_running = false :\n\tset(val):\n\t\t_is_running = val\n\t\t_disable_run_buttons(_is_running)\n\nvar _gut_config = load('res://addons/gut/gut_config.gd').new()\nvar _gut_config_gui = null\nvar _gut_plugin = null\nvar _light_color = Color(0, 0, 0, .5) :\n\tset(val):\n\t\t_light_color = val\n\t\tif(is_inside_tree()):\n\t\t\t_ctrls.light.queue_redraw()\nvar _panel_button = null\nvar _user_prefs = null\nvar _shell_out_panel = null\n\n\nvar menu_manager = null :\n\tset(val):\n\t\tmenu_manager = val\n\t\tif(val != null):\n\t\t\t_apply_shortcuts()\n\t\t\tmenu_manager.toggle_windowed.connect(_on_toggle_windowed)\n\t\t\tmenu_manager.about.connect(show_about)\n\t\t\tmenu_manager.run_all.connect(_run_all)\n\t\t\tmenu_manager.show_gut.connect(_on_show_gut)\n\n\n@onready var _ctrls = {\n\tabout = %ExtraButtons/About,\n\tlight = %StatusIndicator,\n\toutput_button = %ExtraButtons/OutputBtn,\n\trun_button = $layout/ControlBar/RunAll,\n\trun_externally_dialog = $ShellOutOptions,\n\trun_mode = %ExtraButtons/RunMode,\n\trun_at_cursor = $layout/ControlBar/RunAtCursor,\n\trun_results_button = %ExtraButtons/RunResultsBtn,\n\tsettings = $layout/RSplit/sc/Settings,\n\tsettings_button = %ExtraButtons/Settings,\n\tshortcut_dialog = $ShortcutDialog,\n\tshortcuts_button = %ExtraButtons/Shortcuts,\n\n\tresults = {\n\t\tbar = $layout/ControlBar2,\n\t\terrors = %errors_value,\n\t\tfailing = %failing_value,\n\t\torphans = %orphans_value,\n\t\tpassing = %passing_value,\n\t\tpending = %pending_value,\n\t\twarnings = %warnings_value,\n\t},\n}\n\n@onready var results_v_split = %VSplitResults\n@onready var results_h_split = %HSplitResults\n@onready var results_tree = %RunResults\n@onready var results_text = %OutputText\n@onready var make_floating_btn = %MakeFloating\n\n\nfunc _ready():\n\tif(get_parent() is SubViewport):\n\t\treturn\n\n\tGutEditorGlobals.create_temp_directory()\n\n\t_user_prefs = GutEditorGlobals.user_prefs\n\t_gut_config_gui = GutConfigGui.new(_ctrls.settings)\n\n\t_ctrls.results.bar.connect('draw', _on_results_bar_draw.bind(_ctrls.results.bar))\n\thide_settings(!_ctrls.settings_button.button_pressed)\n\n\t_gut_config.load_options(GutEditorGlobals.editor_run_gut_config_path)\n\t_gut_config_gui.set_options(_gut_config.options)\n\n\t_ctrls.shortcuts_button.icon = get_theme_icon('Shortcut', 'EditorIcons')\n\t_ctrls.settings_button.icon = get_theme_icon('Tools', 'EditorIcons')\n\t_ctrls.run_results_button.icon = get_theme_icon('AnimationTrackGroup', 'EditorIcons') # Tree\n\t_ctrls.output_button.icon = get_theme_icon('Font', 'EditorIcons')\n\tmake_floating_btn.icon = get_theme_icon(\"MakeFloating\", 'EditorIcons')\n\tmake_floating_btn.text = ''\n\t_ctrls.about.icon = get_theme_icon('Info', 'EditorIcons')\n\t_ctrls.about.text = ''\n\t_ctrls.run_mode.icon = get_theme_icon(\"ViewportSpeed\", 'EditorIcons')\n\n\tresults_tree.set_output_control(results_text)\n\n\tvar check_import = load('res://addons/gut/images/HSplitContainer.svg')\n\tif(check_import == null):\n\t\tresults_tree.add_centered_text(\"GUT got some new images that are not imported yet.  Please restart Godot.\")\n\t\tprint('GUT got some new images that are not imported yet.  Please restart Godot.')\n\telse:\n\t\tresults_tree.add_centered_text(\"Let's run some tests!\")\n\n\t_ctrls.run_externally_dialog.load_from_file()\n\t_apply_options_to_controls()\n\n\tresults_vert_layout()\n\n\nfunc _process(_delta):\n\tif(_is_running):\n\t\tif(_ctrls.run_externally_dialog.should_run_externally()):\n\t\t\tif(!is_instance_valid(_shell_out_panel)):\n\t\t\t\t_is_running = false\n\t\t\t\tshow_me()\n\t\telif(!_interface.is_playing_scene()):\n\t\t\t_is_running = false\n\t\t\tresults_text.add_text(\"\\ndone\")\n\t\t\tload_result_output()\n\t\t\tshow_me()\n\n\n# ---------------\n# Private\n# ---------------\nfunc _apply_options_to_controls():\n\thide_settings(_user_prefs.hide_settings.value)\n\thide_result_tree(_user_prefs.hide_result_tree.value)\n\thide_output_text(_user_prefs.hide_output_text.value)\n\tresults_tree.set_show_orphans(!_gut_config.options.hide_orphans)\n\tvar shell_dialog_size = _user_prefs.run_externally_options_dialog_size.value\n\n\tif(shell_dialog_size != Vector2i(-1, -1)):\n\t\t_ctrls.run_externally_dialog.size = Vector2i(shell_dialog_size)\n\n\tif(_user_prefs.shortcuts_dialog_size.value != Vector2i(-1, -1)):\n\t\t_ctrls.shortcut_dialog.size = _user_prefs.shortcuts_dialog_size.value\n\n\tvar mode_ind = 'Ed'\n\tif(_ctrls.run_externally_dialog.run_mode == _ctrls.run_externally_dialog.RUN_MODE_BLOCKING):\n\t\tmode_ind = 'ExB'\n\telif(_ctrls.run_externally_dialog.run_mode == _ctrls.run_externally_dialog.RUN_MODE_NON_BLOCKING):\n\t\tmode_ind = 'ExN'\n\t_ctrls.run_mode.text = mode_ind\n\n\t_ctrls.run_at_cursor.apply_gut_config(_gut_config)\n\n\n\nfunc _disable_run_buttons(should):\n\t_ctrls.run_button.disabled = should\n\t_ctrls.run_at_cursor.disabled = should\n\n\nfunc _is_test_script(script):\n\tvar from = script.get_base_script()\n\twhile(from and from.resource_path != 'res://addons/gut/test.gd'):\n\t\tfrom = from.get_base_script()\n\n\treturn from != null\n\n\nfunc _show_errors(errs):\n\tresults_text.clear()\n\tvar text = \"Cannot run tests, you have a configuration error:\\n\"\n\tfor e in errs:\n\t\ttext += str('*  ', e, \"\\n\")\n\ttext += \"Check your settings ----->\"\n\tresults_text.add_text(text)\n\thide_output_text(false)\n\thide_settings(false)\n\n\nfunc _save_user_prefs():\n\t_user_prefs.hide_settings.value = !_ctrls.settings_button.button_pressed\n\t_user_prefs.hide_result_tree.value = !_ctrls.run_results_button.button_pressed\n\t_user_prefs.hide_output_text.value = !_ctrls.output_button.button_pressed\n\t_user_prefs.shortcuts_dialog_size.value = _ctrls.shortcut_dialog.size\n\n\t_user_prefs.run_externally.value = _ctrls.run_externally_dialog.run_mode != _ctrls.run_externally_dialog.RUN_MODE_EDITOR\n\t_user_prefs.run_externally_options_dialog_size.value = _ctrls.run_externally_dialog.size\n\n\t_user_prefs.save_it()\n\n\nfunc _save_config():\n\t_save_user_prefs()\n\n\t_gut_config.options = _gut_config_gui.get_options(_gut_config.options)\n\tvar w_result = _gut_config.write_options(GutEditorGlobals.editor_run_gut_config_path)\n\tif(w_result != OK):\n\t\tpush_error(str('Could not write options to ', GutEditorGlobals.editor_run_gut_config_path, ': ', w_result))\n\telse:\n\t\t_gut_config_gui.mark_saved()\n\n\nfunc _run_externally():\n\t_shell_out_panel = GutUtils.RunExternallyScene.instantiate()\n\t_shell_out_panel.bottom_panel = self\n\t_shell_out_panel.blocking_mode = _ctrls.run_externally_dialog.run_mode\n\t_shell_out_panel.additional_arguments = _ctrls.run_externally_dialog.get_additional_arguments_array()\n\n\tadd_child(_shell_out_panel)\n\t_shell_out_panel.run_tests()\n\n\nfunc _run_tests():\n\tshow_me()\n\tif(_is_running):\n\t\tpush_error(\"GUT:  Cannot run tests, tests are already running.\")\n\t\treturn\n\n\tclear_results()\n\tGutEditorGlobals.create_temp_directory()\n\t_light_color = Color.BLUE\n\n\tvar issues = _gut_config_gui.get_config_issues()\n\tif(issues.size() > 0):\n\t\t_show_errors(issues)\n\t\treturn\n\n\twrite_file(GutEditorGlobals.editor_run_bbcode_results_path, 'Run in progress')\n\twrite_file(GutEditorGlobals.editor_run_json_results_path, '')\n\t_save_config()\n\t_apply_options_to_controls()\n\n\tresults_text.clear()\n\tresults_tree.clear()\n\tresults_tree.add_centered_text('Running...')\n\n\t_is_running = true\n\tresults_text.add_text('Running...')\n\n\tif(_ctrls.run_externally_dialog.should_run_externally()):\n\t\t_gut_plugin.make_bottom_panel_item_visible(self)\n\t\t_run_externally()\n\telse:\n\t\t_interface.play_custom_scene('res://addons/gut/gui/run_from_editor.tscn')\n\n\nfunc _apply_shortcuts():\n\tif(menu_manager != null):\n\t\tmenu_manager.apply_gut_shortcuts(_ctrls.shortcut_dialog)\n\n\t_ctrls.run_button.shortcut = \\\n\t\t_ctrls.shortcut_dialog.scbtn_run_all.get_shortcut()\n\t_ctrls.run_at_cursor.get_script_button().shortcut = \\\n\t\t_ctrls.shortcut_dialog.scbtn_run_current_script.get_shortcut()\n\t_ctrls.run_at_cursor.get_inner_button().shortcut = \\\n\t\t_ctrls.shortcut_dialog.scbtn_run_current_inner.get_shortcut()\n\t_ctrls.run_at_cursor.get_test_button().shortcut = \\\n\t\t_ctrls.shortcut_dialog.scbtn_run_current_test.get_shortcut()\n\t# Took this out because it seems to break using the shortcut when docked.\n\t# Though it does allow the shortcut to work when windowed.  Shortcuts\n\t# are weird.\n\t# make_floating_btn.shortcut = \\\n\t# \t_ctrls.shortcut_dialog.scbtn_windowed.get_shortcut()\n\n\n\tif(_panel_button != null):\n\t\t_panel_button.shortcut = _ctrls.shortcut_dialog.scbtn_panel.get_shortcut()\n\n\nfunc _run_all():\n\t_gut_config.options.selected = null\n\t_gut_config.options.inner_class = null\n\t_gut_config.options.unit_test_name = null\n\n\t_run_tests()\n\n\n# ---------------\n# Events\n# ---------------\nfunc _on_results_bar_draw(bar):\n\tbar.draw_rect(Rect2(Vector2(0, 0), bar.size), Color(0, 0, 0, .2))\n\n\nfunc _on_Light_draw():\n\tvar l = _ctrls.light\n\tl.draw_circle(Vector2(l.size.x / 2, l.size.y / 2), l.size.x / 2, _light_color)\n\n\nfunc _on_RunAll_pressed():\n\t_run_all()\n\n\nfunc _on_Shortcuts_pressed():\n\t_ctrls.shortcut_dialog.popup_centered()\n\n\nfunc _on_sortcut_dialog_confirmed() -> void:\n\t_apply_shortcuts()\n\t_ctrls.shortcut_dialog.save_shortcuts()\n\t_save_user_prefs()\n\n\nfunc _on_RunAtCursor_run_tests(what):\n\t_gut_config.options.selected = what.script\n\t_gut_config.options.inner_class = what.inner_class\n\t_gut_config.options.unit_test_name = what.method\n\n\t_run_tests()\n\n\nfunc _on_Settings_pressed():\n\thide_settings(!_ctrls.settings_button.button_pressed)\n\t_save_config()\n\n\nfunc _on_OutputBtn_pressed():\n\thide_output_text(!_ctrls.output_button.button_pressed)\n\t_save_config()\n\n\nfunc _on_RunResultsBtn_pressed():\n\thide_result_tree(! _ctrls.run_results_button.button_pressed)\n\t_save_config()\n\n\n# Currently not used, but will be when I figure out how to put\n# colors into the text results\nfunc _on_UseColors_pressed():\n\tpass\n\n\nfunc _on_shell_out_options_confirmed() -> void:\n\t_ctrls.run_externally_dialog.save_to_file()\n\t_save_user_prefs()\n\t_apply_options_to_controls()\n\n\nfunc _on_run_mode_pressed() -> void:\n\t_ctrls.run_externally_dialog.popup_centered()\n\n\nfunc _on_toggle_windowed():\n\t_gut_plugin.toggle_windowed()\n\n\nfunc _on_to_window_pressed() -> void:\n\t_gut_plugin.toggle_windowed()\n\n\nfunc _on_show_gut() -> void:\n\tshow_hide()\n\n\nfunc _on_about_pressed() -> void:\n\tshow_about()\n\n# ---------------\n# Public\n# ---------------\nfunc load_shortcuts():\n\t_ctrls.shortcut_dialog.load_shortcuts()\n\t_apply_shortcuts()\n\n\nfunc hide_result_tree(should):\n\tresults_tree.visible = !should\n\t_ctrls.run_results_button.button_pressed = !should\n\n\nfunc hide_settings(should):\n\tvar s_scroll = _ctrls.settings.get_parent()\n\ts_scroll.visible = !should\n\n\t# collapse only collapses the first control, so we move\n\t# settings around to be the collapsed one\n\tif(should):\n\t\ts_scroll.get_parent().move_child(s_scroll, 0)\n\telse:\n\t\ts_scroll.get_parent().move_child(s_scroll, 1)\n\n\t$layout/RSplit.collapsed = should\n\t_ctrls.settings_button.button_pressed = !should\n\n\nfunc hide_output_text(should):\n\tresults_text.visible = !should\n\t_ctrls.output_button.button_pressed = !should\n\n\nfunc clear_results():\n\t_light_color = Color(0, 0, 0, .5)\n\n\t_ctrls.results.passing.text = \"0\"\n\t_ctrls.results.passing.get_parent().visible = false\n\n\t_ctrls.results.failing.text = \"0\"\n\t_ctrls.results.failing.get_parent().visible = false\n\n\t_ctrls.results.pending.text = \"0\"\n\t_ctrls.results.pending.get_parent().visible = false\n\n\t_ctrls.results.errors.text = \"0\"\n\t_ctrls.results.errors.get_parent().visible = false\n\n\t_ctrls.results.warnings.text = \"0\"\n\t_ctrls.results.warnings.get_parent().visible = false\n\n\t_ctrls.results.orphans.text = \"0\"\n\t_ctrls.results.orphans.get_parent().visible = false\n\n\nfunc load_result_json():\n\tvar summary = get_file_as_text(GutEditorGlobals.editor_run_json_results_path)\n\tvar test_json_conv = JSON.new()\n\tif (test_json_conv.parse(summary) != OK):\n\t\treturn\n\tvar results = test_json_conv.get_data()\n\n\tresults_tree.load_json_results(results)\n\n\tvar summary_json = results['test_scripts']['props']\n\t_ctrls.results.passing.text = str(int(summary_json.passing))\n\t_ctrls.results.passing.get_parent().visible = true\n\n\t_ctrls.results.failing.text = str(int(summary_json.failures))\n\t_ctrls.results.failing.get_parent().visible = true\n\n\t_ctrls.results.pending.text = str(int(summary_json.pending) + int(summary_json.risky))\n\t_ctrls.results.pending.get_parent().visible = _ctrls.results.pending.text != '0'\n\n\t_ctrls.results.errors.text = str(int(summary_json.errors))\n\t_ctrls.results.errors.get_parent().visible = _ctrls.results.errors.text != '0'\n\n\t_ctrls.results.warnings.text = str(int(summary_json.warnings))\n\t_ctrls.results.warnings.get_parent().visible = _ctrls.results.warnings.text != '0'\n\n\t_ctrls.results.orphans.text = str(int(summary_json.orphans))\n\t_ctrls.results.orphans.get_parent().visible = _ctrls.results.orphans.text != '0' and !_gut_config.options.hide_orphans\n\n\tif(summary_json.tests == 0):\n\t\t_light_color = Color(1, 0, 0, .75)\n\telif(summary_json.failures != 0):\n\t\t_light_color = Color(1, 0, 0, .75)\n\telif(summary_json.pending != 0 or summary_json.risky != 0):\n\t\t_light_color = Color(1, 1, 0, .75)\n\telse:\n\t\t_light_color = Color(0, 1, 0, .75)\n\n\t_ctrls.light.visible = true\n\n\nfunc load_result_text():\n\tresults_text.load_file(GutEditorGlobals.editor_run_bbcode_results_path)\n\n\nfunc load_result_output():\n\tload_result_text()\n\tload_result_json()\n\n\nfunc set_interface(value):\n\t_interface = value\n\tresults_tree.set_interface(_interface)\n\n\nfunc set_plugin(value):\n\t_gut_plugin = value\n\n\nfunc set_panel_button(value):\n\t_panel_button = value\n\n\nfunc write_file(path, content):\n\tvar f = FileAccess.open(path, FileAccess.WRITE)\n\tif(f != null):\n\t\tf.store_string(content)\n\tf = null;\n\n\treturn FileAccess.get_open_error()\n\n\nfunc get_file_as_text(path):\n\tvar to_return = ''\n\tvar f = FileAccess.open(path, FileAccess.READ)\n\tif(f != null):\n\t\tto_return = f.get_as_text()\n\tf = null\n\treturn to_return\n\n\nfunc get_text_output_control():\n\treturn results_text\n\n\nfunc add_output_text(text):\n\tresults_text.add_text(text)\n\n\nfunc show_about():\n\tvar about = AboutWindow.instantiate()\n\tadd_child(about)\n\tabout.popup_centered()\n\tabout.confirmed.connect(about.queue_free)\n\n\nfunc show_me():\n\tif(owner is Window):\n\t\towner.grab_focus()\n\telse:\n\t\t_gut_plugin.make_bottom_panel_item_visible(self)\n\n\nfunc show_hide():\n\tif(owner is Window):\n\t\tif(owner.has_focus()):\n\t\t\tvar win_to_focus_on = EditorInterface.get_editor_main_screen().get_parent()\n\t\t\twhile(win_to_focus_on != null and win_to_focus_on is not Window):\n\t\t\t\twin_to_focus_on = win_to_focus_on.get_parent()\n\t\t\tif(win_to_focus_on != null):\n\t\t\t\twin_to_focus_on.grab_focus()\n\t\telse:\n\t\t\towner.grab_focus()\n\telse:\n\t\tpass\n\t\t# We don't have to do anything when we are docked because the GUT\n\t\t# bottom panel has the shortcut and it does the toggling all on its\n\t\t# own.\n\n\nfunc get_shortcut_dialog():\n\treturn _ctrls.shortcut_dialog\n\n\nfunc results_vert_layout():\n\tif(results_tree.get_parent() != results_v_split):\n\t\tresults_tree.reparent(results_v_split)\n\t\tresults_text.reparent(results_v_split)\n\t\tresults_v_split.visible = true\n\t\tresults_h_split.visible = false\n\n\nfunc results_horiz_layout():\n\tif(results_tree.get_parent() != results_h_split):\n\t\tresults_tree.reparent(results_h_split)\n\t\tresults_text.reparent(results_h_split)\n\t\tresults_v_split.visible = false\n\t\tresults_h_split.visible = true\n"
  },
  {
    "path": "demo/addons/gut/gui/GutBottomPanel.gd.uid",
    "content": "uid://dtvnb0xatk0my\n"
  },
  {
    "path": "demo/addons/gut/gui/GutBottomPanel.tscn",
    "content": "[gd_scene load_steps=10 format=3 uid=\"uid://b3bostcslstem\"]\n\n[ext_resource type=\"Script\" uid=\"uid://dtvnb0xatk0my\" path=\"res://addons/gut/gui/GutBottomPanel.gd\" id=\"1\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://0yunjxtaa8iw\" path=\"res://addons/gut/gui/RunAtCursor.tscn\" id=\"3\"]\n[ext_resource type=\"Texture2D\" uid=\"uid://cr6tvdv0ve6cv\" path=\"res://addons/gut/gui/play.png\" id=\"4\"]\n[ext_resource type=\"Texture2D\" uid=\"uid://bvo0uao7deu0q\" path=\"res://addons/gut/icon.png\" id=\"4_xv2r3\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://4gyyn12um08h\" path=\"res://addons/gut/gui/RunResults.tscn\" id=\"5\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://bqmo4dj64c7yl\" path=\"res://addons/gut/gui/OutputText.tscn\" id=\"6\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://dj5ve0bq7xa5j\" path=\"res://addons/gut/gui/ShortcutDialog.tscn\" id=\"7_srqj5\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://ckv5eh8xyrwbk\" path=\"res://addons/gut/gui/ShellOutOptions.tscn\" id=\"7_xv2r3\"]\n\n[sub_resource type=\"Shortcut\" id=\"9\"]\n\n[node name=\"GutBottomPanel\" type=\"Control\"]\ncustom_minimum_size = Vector2(250, 250)\nlayout_mode = 3\nanchor_left = -0.0025866\nanchor_top = -0.00176575\nanchor_right = 0.997413\nanchor_bottom = 0.998234\noffset_left = 2.64868\noffset_top = 1.05945\noffset_right = 2.64862\noffset_bottom = 1.05945\nscript = ExtResource(\"1\")\n\n[node name=\"layout\" type=\"VBoxContainer\" parent=\".\"]\nlayout_mode = 0\nanchor_right = 1.0\nanchor_bottom = 1.0\n\n[node name=\"ControlBar\" type=\"HBoxContainer\" parent=\"layout\"]\nlayout_mode = 2\n\n[node name=\"RunAll\" type=\"Button\" parent=\"layout/ControlBar\"]\nlayout_mode = 2\nsize_flags_vertical = 11\nshortcut = SubResource(\"9\")\ntext = \"Run All\"\nicon = ExtResource(\"4\")\n\n[node name=\"Sep3\" type=\"ColorRect\" parent=\"layout/ControlBar\"]\ncustom_minimum_size = Vector2(1, 2.08165e-12)\nlayout_mode = 2\n\n[node name=\"RunAtCursor\" parent=\"layout/ControlBar\" instance=ExtResource(\"3\")]\ncustom_minimum_size = Vector2(648, 0)\nlayout_mode = 2\n\n[node name=\"CenterContainer2\" type=\"CenterContainer\" parent=\"layout/ControlBar\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"MakeFloating\" type=\"Button\" parent=\"layout/ControlBar\"]\nunique_name_in_owner = true\nlayout_mode = 2\ntooltip_text = \"Move the GUT panel to a window.\"\nicon = ExtResource(\"4_xv2r3\")\nflat = true\n\n[node name=\"ControlBar2\" type=\"HBoxContainer\" parent=\"layout\"]\nlayout_mode = 2\n\n[node name=\"Sep2\" type=\"ColorRect\" parent=\"layout/ControlBar2\"]\ncustom_minimum_size = Vector2(1, 2.08165e-12)\nlayout_mode = 2\ncolor = Color(1, 1, 1, 0)\n\n[node name=\"StatusIndicator\" type=\"Control\" parent=\"layout/ControlBar2\"]\nunique_name_in_owner = true\ncustom_minimum_size = Vector2(30, 30)\nlayout_mode = 2\n\n[node name=\"Passing\" type=\"HBoxContainer\" parent=\"layout/ControlBar2\"]\nvisible = false\nlayout_mode = 2\n\n[node name=\"Sep\" type=\"ColorRect\" parent=\"layout/ControlBar2/Passing\"]\ncustom_minimum_size = Vector2(1, 2.08165e-12)\nlayout_mode = 2\n\n[node name=\"label\" type=\"Label\" parent=\"layout/ControlBar2/Passing\"]\nlayout_mode = 2\ntext = \"Pass\"\n\n[node name=\"passing_value\" type=\"Label\" parent=\"layout/ControlBar2/Passing\"]\nunique_name_in_owner = true\nlayout_mode = 2\ntext = \"---\"\n\n[node name=\"Failing\" type=\"HBoxContainer\" parent=\"layout/ControlBar2\"]\nvisible = false\nlayout_mode = 2\n\n[node name=\"Sep\" type=\"ColorRect\" parent=\"layout/ControlBar2/Failing\"]\ncustom_minimum_size = Vector2(1, 2.08165e-12)\nlayout_mode = 2\n\n[node name=\"label\" type=\"Label\" parent=\"layout/ControlBar2/Failing\"]\nlayout_mode = 2\ntext = \"Fail\"\n\n[node name=\"failing_value\" type=\"Label\" parent=\"layout/ControlBar2/Failing\"]\nunique_name_in_owner = true\nlayout_mode = 2\ntext = \"---\"\n\n[node name=\"Pending\" type=\"HBoxContainer\" parent=\"layout/ControlBar2\"]\nvisible = false\nlayout_mode = 2\n\n[node name=\"Sep\" type=\"ColorRect\" parent=\"layout/ControlBar2/Pending\"]\ncustom_minimum_size = Vector2(1, 2.08165e-12)\nlayout_mode = 2\n\n[node name=\"label\" type=\"Label\" parent=\"layout/ControlBar2/Pending\"]\nlayout_mode = 2\ntext = \"Risky\"\n\n[node name=\"pending_value\" type=\"Label\" parent=\"layout/ControlBar2/Pending\"]\nunique_name_in_owner = true\nlayout_mode = 2\ntext = \"---\"\n\n[node name=\"Orphans\" type=\"HBoxContainer\" parent=\"layout/ControlBar2\"]\nvisible = false\nlayout_mode = 2\n\n[node name=\"Sep\" type=\"ColorRect\" parent=\"layout/ControlBar2/Orphans\"]\ncustom_minimum_size = Vector2(1, 2.08165e-12)\nlayout_mode = 2\n\n[node name=\"label\" type=\"Label\" parent=\"layout/ControlBar2/Orphans\"]\nlayout_mode = 2\ntext = \"Orphans\"\n\n[node name=\"orphans_value\" type=\"Label\" parent=\"layout/ControlBar2/Orphans\"]\nunique_name_in_owner = true\nlayout_mode = 2\ntext = \"---\"\n\n[node name=\"Errors\" type=\"HBoxContainer\" parent=\"layout/ControlBar2\"]\nvisible = false\nlayout_mode = 2\n\n[node name=\"Sep\" type=\"ColorRect\" parent=\"layout/ControlBar2/Errors\"]\ncustom_minimum_size = Vector2(1, 2.08165e-12)\nlayout_mode = 2\n\n[node name=\"label\" type=\"Label\" parent=\"layout/ControlBar2/Errors\"]\nlayout_mode = 2\ntext = \"Errors\"\n\n[node name=\"errors_value\" type=\"Label\" parent=\"layout/ControlBar2/Errors\"]\nunique_name_in_owner = true\nlayout_mode = 2\ntext = \"---\"\n\n[node name=\"Warnings\" type=\"HBoxContainer\" parent=\"layout/ControlBar2\"]\nvisible = false\nlayout_mode = 2\n\n[node name=\"Sep\" type=\"ColorRect\" parent=\"layout/ControlBar2/Warnings\"]\ncustom_minimum_size = Vector2(1, 2.08165e-12)\nlayout_mode = 2\n\n[node name=\"label\" type=\"Label\" parent=\"layout/ControlBar2/Warnings\"]\nlayout_mode = 2\ntext = \"Warnings\"\n\n[node name=\"warnings_value\" type=\"Label\" parent=\"layout/ControlBar2/Warnings\"]\nunique_name_in_owner = true\nlayout_mode = 2\ntext = \"---\"\n\n[node name=\"CenterContainer\" type=\"CenterContainer\" parent=\"layout/ControlBar2\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"ExtraButtons\" type=\"HBoxContainer\" parent=\"layout/ControlBar2\"]\nunique_name_in_owner = true\nlayout_mode = 2\n\n[node name=\"Sep1\" type=\"ColorRect\" parent=\"layout/ControlBar2/ExtraButtons\"]\ncustom_minimum_size = Vector2(1, 2.08165e-12)\nlayout_mode = 2\n\n[node name=\"RunMode\" type=\"Button\" parent=\"layout/ControlBar2/ExtraButtons\"]\nlayout_mode = 2\ntooltip_text = \"Run Mode.  Run tests through the editor or externally in a different process.\"\ntext = \"ExN\"\nicon = ExtResource(\"4_xv2r3\")\n\n[node name=\"Sep2\" type=\"ColorRect\" parent=\"layout/ControlBar2/ExtraButtons\"]\ncustom_minimum_size = Vector2(1, 2.08165e-12)\nlayout_mode = 2\n\n[node name=\"RunResultsBtn\" type=\"Button\" parent=\"layout/ControlBar2/ExtraButtons\"]\nlayout_mode = 2\ntooltip_text = \"Show/Hide Result Tree\"\ntoggle_mode = true\nbutton_pressed = true\nicon = ExtResource(\"4_xv2r3\")\n\n[node name=\"OutputBtn\" type=\"Button\" parent=\"layout/ControlBar2/ExtraButtons\"]\nlayout_mode = 2\ntooltip_text = \"Show/Hide Text Output\"\ntoggle_mode = true\nbutton_pressed = true\nicon = ExtResource(\"4_xv2r3\")\n\n[node name=\"Settings\" type=\"Button\" parent=\"layout/ControlBar2/ExtraButtons\"]\nlayout_mode = 2\ntooltip_text = \"GUT Settings\"\ntoggle_mode = true\nbutton_pressed = true\nicon = ExtResource(\"4_xv2r3\")\n\n[node name=\"Sep3\" type=\"ColorRect\" parent=\"layout/ControlBar2/ExtraButtons\"]\ncustom_minimum_size = Vector2(1, 2.08165e-12)\nlayout_mode = 2\n\n[node name=\"Shortcuts\" type=\"Button\" parent=\"layout/ControlBar2/ExtraButtons\"]\nlayout_mode = 2\nsize_flags_vertical = 11\ntooltip_text = \"GUT Shortcuts\"\nicon = ExtResource(\"4_xv2r3\")\n\n[node name=\"About\" type=\"Button\" parent=\"layout/ControlBar2/ExtraButtons\"]\nlayout_mode = 2\ntooltip_text = \"About\"\ntext = \"A\"\n\n[node name=\"RSplit\" type=\"HSplitContainer\" parent=\"layout\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n\n[node name=\"CResults\" type=\"VBoxContainer\" parent=\"layout/RSplit\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n\n[node name=\"HSplitResults\" type=\"HSplitContainer\" parent=\"layout/RSplit/CResults\"]\nunique_name_in_owner = true\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n\n[node name=\"RunResults\" parent=\"layout/RSplit/CResults/HSplitResults\" instance=ExtResource(\"5\")]\nunique_name_in_owner = true\ncustom_minimum_size = Vector2(255, 255)\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n\n[node name=\"OutputText\" parent=\"layout/RSplit/CResults/HSplitResults\" instance=ExtResource(\"6\")]\nunique_name_in_owner = true\nlayout_mode = 2\n\n[node name=\"VSplitResults\" type=\"VSplitContainer\" parent=\"layout/RSplit/CResults\"]\nunique_name_in_owner = true\nvisible = false\nlayout_mode = 2\nsize_flags_vertical = 3\n\n[node name=\"sc\" type=\"ScrollContainer\" parent=\"layout/RSplit\"]\ncustom_minimum_size = Vector2(500, 2.08165e-12)\nlayout_mode = 2\nsize_flags_vertical = 3\n\n[node name=\"Settings\" type=\"VBoxContainer\" parent=\"layout/RSplit/sc\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n\n[node name=\"ShortcutDialog\" parent=\".\" instance=ExtResource(\"7_srqj5\")]\nvisible = false\n\n[node name=\"ShellOutOptions\" parent=\".\" instance=ExtResource(\"7_xv2r3\")]\nsize = Vector2i(1300, 1336)\nvisible = false\n\n[connection signal=\"pressed\" from=\"layout/ControlBar/RunAll\" to=\".\" method=\"_on_RunAll_pressed\"]\n[connection signal=\"run_tests\" from=\"layout/ControlBar/RunAtCursor\" to=\".\" method=\"_on_RunAtCursor_run_tests\"]\n[connection signal=\"pressed\" from=\"layout/ControlBar/MakeFloating\" to=\".\" method=\"_on_to_window_pressed\"]\n[connection signal=\"draw\" from=\"layout/ControlBar2/StatusIndicator\" to=\".\" method=\"_on_Light_draw\"]\n[connection signal=\"pressed\" from=\"layout/ControlBar2/ExtraButtons/RunMode\" to=\".\" method=\"_on_run_mode_pressed\"]\n[connection signal=\"pressed\" from=\"layout/ControlBar2/ExtraButtons/RunResultsBtn\" to=\".\" method=\"_on_RunResultsBtn_pressed\"]\n[connection signal=\"pressed\" from=\"layout/ControlBar2/ExtraButtons/OutputBtn\" to=\".\" method=\"_on_OutputBtn_pressed\"]\n[connection signal=\"pressed\" from=\"layout/ControlBar2/ExtraButtons/Settings\" to=\".\" method=\"_on_Settings_pressed\"]\n[connection signal=\"pressed\" from=\"layout/ControlBar2/ExtraButtons/Shortcuts\" to=\".\" method=\"_on_Shortcuts_pressed\"]\n[connection signal=\"pressed\" from=\"layout/ControlBar2/ExtraButtons/About\" to=\".\" method=\"_on_about_pressed\"]\n[connection signal=\"confirmed\" from=\"ShortcutDialog\" to=\".\" method=\"_on_sortcut_dialog_confirmed\"]\n[connection signal=\"confirmed\" from=\"ShellOutOptions\" to=\".\" method=\"_on_shell_out_options_confirmed\"]\n"
  },
  {
    "path": "demo/addons/gut/gui/GutControl.gd",
    "content": "@tool\nextends Control\n\nconst RUNNER_JSON_PATH = 'res://.gut_editor_config.json'\n\nvar GutConfig = load('res://addons/gut/gut_config.gd')\nvar GutRunnerScene = load('res://addons/gut/gui/GutRunner.tscn')\nvar GutConfigGui = load('res://addons/gut/gui/gut_config_gui.gd')\n\nvar _config = GutConfig.new()\nvar _config_gui = null\nvar _gut_runner = null\nvar _tree_root : TreeItem = null\n\nvar _script_icon = load('res://addons/gut/images/Script.svg')\nvar _folder_icon = load('res://addons/gut/images/Folder.svg')\n\nvar _tree_scripts = {}\nvar _tree_directories = {}\n\nconst TREE_SCRIPT = 'Script'\nconst TREE_DIR = 'Directory'\n\n@onready var _ctrls = {\n\trun_tests_button = $VBox/Buttons/RunTests,\n\trun_selected = $VBox/Buttons/RunSelected,\n\ttest_tree = $VBox/Tabs/Tests,\n\tsettings_vbox = $VBox/Tabs/SettingsScroll/Settings,\n\ttabs = $VBox/Tabs,\n\tbg = $Bg\n}\n\n@export var bg_color : Color = Color(.36, .36, .36) :\n\tget: return bg_color\n\tset(val):\n\t\tbg_color = val\n\t\tif(is_inside_tree()):\n\t\t\t$Bg.color = bg_color\n\n\nfunc _ready():\n\tif Engine.is_editor_hint():\n\t\treturn\n\n\t_gut_runner = GutRunnerScene.instantiate()\n\t$Bg.color = bg_color\n\t_ctrls.tabs.set_tab_title(0, 'Tests')\n\t_ctrls.tabs.set_tab_title(1, 'Settings')\n\n\t_config_gui = GutConfigGui.new(_ctrls.settings_vbox)\n\n\t_ctrls.test_tree.hide_root = true\n\tadd_child(_gut_runner)\n\n\t# TODO This might not need to be called deferred after changing GutUtils to\n\t# an all static class.\n\tcall_deferred('_post_ready')\n\n\nfunc _draw():\n\tif Engine.is_editor_hint():\n\t\treturn\n\n\tvar gut = _gut_runner.get_gut()\n\tif(!gut.is_running()):\n\t\tvar r = Rect2(Vector2(0, 0), get_rect().size)\n\t\tdraw_rect(r, Color.BLACK, false, 2)\n\n\nfunc _post_ready():\n\tvar gut = _gut_runner.get_gut()\n\tgut.start_run.connect(_on_gut_run_started)\n\tgut.end_run.connect(_on_gut_run_ended)\n\t_refresh_tree_and_settings()\n\n\nfunc _set_meta_for_script_tree_item(item, script, test=null):\n\tvar meta = {\n\t\ttype = TREE_SCRIPT,\n\t\tscript = script.path,\n\t\tinner_class = script.inner_class_name,\n\t\ttest = ''\n\t}\n\n\tif(test != null):\n\t\tmeta.test = test.name\n\n\titem.set_metadata(0, meta)\n\n\nfunc _set_meta_for_directory_tree_item(item, path, temp_item):\n\tvar meta = {\n\t\ttype = TREE_DIR,\n\t\tpath = path,\n\t\ttemp_item = temp_item\n\t}\n\titem.set_metadata(0, meta)\n\n\nfunc _get_script_tree_item(script, parent_item):\n\tif(!_tree_scripts.has(script.path)):\n\t\tvar item = _ctrls.test_tree.create_item(parent_item)\n\t\titem.set_text(0, script.path.get_file())\n\t\titem.set_icon(0, _script_icon)\n\t\t_tree_scripts[script.path] = item\n\t\t_set_meta_for_script_tree_item(item, script)\n\n\treturn _tree_scripts[script.path]\n\n\nfunc _get_directory_tree_item(path):\n\tvar parent = _tree_root\n\tif(!_tree_directories.has(path)):\n\n\t\tvar item : TreeItem = null\n\t\tif(parent != _tree_root):\n\t\t\titem = parent.create_child(0)\n\t\telse:\n\t\t\titem = parent.create_child()\n\n\t\t_tree_directories[path] = item\n\t\titem.collapsed = false\n\t\titem.set_text(0, path)\n\t\titem.set_icon(0, _folder_icon)\n\t\titem.set_icon_modulate(0, Color.ROYAL_BLUE)\n\t\t# temp_item is used in calls with move_before since you must use\n\t\t# move_before or move_after to reparent tree items. This ensures that\n\t\t# there is an item on all directories.  These are deleted later.\n\t\tvar temp_item = item.create_child()\n\t\ttemp_item.set_text(0, '<temp>')\n\n\t\t_set_meta_for_directory_tree_item(item, path, temp_item)\n\n\treturn _tree_directories[path]\n\n\nfunc _find_dir_item_to_move_before(path):\n\tvar max_matching_len = 0\n\tvar best_parent = null\n\n\t# Go through all the directory items finding the one that has the longest\n\t# path that contains our path.\n\tfor key in _tree_directories.keys():\n\t\tif(path != key and path.begins_with(key) and key.length() > max_matching_len):\n\t\t\t\tmax_matching_len = key.length()\n\t\t\t\tbest_parent = _tree_directories[key]\n\n\tvar to_return = null\n\tif(best_parent != null):\n\t\tto_return = best_parent.get_metadata(0).temp_item\n\treturn to_return\n\n\nfunc _reorder_dir_items():\n\tvar the_keys = _tree_directories.keys()\n\tthe_keys.sort()\n\tfor key in _tree_directories.keys():\n\t\tvar to_move = _tree_directories[key]\n\t\tto_move.collapsed = false\n\t\tvar move_before = _find_dir_item_to_move_before(key)\n\t\tif(move_before != null):\n\t\t\tto_move.move_before(move_before)\n\t\t\tvar new_text = key.substr(move_before.get_parent().get_metadata(0).path.length())\n\t\t\tto_move.set_text(0, new_text)\n\n\nfunc _remove_dir_temp_items():\n\tfor key in _tree_directories.keys():\n\t\tvar item = _tree_directories[key].get_metadata(0).temp_item\n\t\t_tree_directories[key].remove_child(item)\n\n\nfunc _add_dir_and_script_tree_items():\n\tvar tree : Tree = _ctrls.test_tree\n\ttree.clear()\n\t_tree_root = _ctrls.test_tree.create_item()\n\n\tvar scripts = _gut_runner.get_gut().get_test_collector().scripts\n\tfor script in scripts:\n\t\tvar dir_item = _get_directory_tree_item(script.path.get_base_dir())\n\t\tvar item = _get_script_tree_item(script, dir_item)\n\n\t\tif(script.inner_class_name != ''):\n\t\t\tvar inner_item = tree.create_item(item)\n\t\t\tinner_item.set_text(0, script.inner_class_name)\n\t\t\t_set_meta_for_script_tree_item(inner_item, script)\n\t\t\titem = inner_item\n\n\t\tfor test in script.tests:\n\t\t\tvar test_item = tree.create_item(item)\n\t\t\ttest_item.set_text(0, test.name)\n\t\t\t_set_meta_for_script_tree_item(test_item, script, test)\n\n\nfunc _populate_tree():\n\t_add_dir_and_script_tree_items()\n\t_tree_root.set_collapsed_recursive(true)\n\t_tree_root.set_collapsed(false)\n\t_reorder_dir_items()\n\t_remove_dir_temp_items()\n\n\nfunc _refresh_tree_and_settings():\n\t_config.apply_options(_gut_runner.get_gut())\n\t_gut_runner.set_gut_config(_config)\n\t_populate_tree()\n\n# ---------------------------\n# Events\n# ---------------------------\nfunc _on_gut_run_started():\n\t_ctrls.run_tests_button.disabled = true\n\t_ctrls.run_selected.visible = false\n\t_ctrls.tabs.visible = false\n\t_ctrls.bg.visible = false\n\t_ctrls.run_tests_button.text = 'Running'\n\tqueue_redraw()\n\n\nfunc _on_gut_run_ended():\n\t_ctrls.run_tests_button.disabled = false\n\t_ctrls.run_selected.visible = true\n\t_ctrls.tabs.visible = true\n\t_ctrls.bg.visible = true\n\t_ctrls.run_tests_button.text = 'Run All'\n\tqueue_redraw()\n\n\nfunc _on_run_tests_pressed():\n\trun_all()\n\n\nfunc _on_run_selected_pressed():\n\trun_selected()\n\n\nfunc _on_tests_item_activated():\n\trun_selected()\n\n# ---------------------------\n# Public\n# ---------------------------\nfunc get_gut():\n\treturn _gut_runner.get_gut()\n\n\nfunc get_config():\n\treturn _config\n\n\nfunc run_all():\n\t_config.options.selected = ''\n\t_config.options.inner_class_name = ''\n\t_config.options.unit_test_name = ''\n\trun_tests()\n\n\nfunc run_tests(options = null):\n\tif(options == null):\n\t\t_config.options = _config_gui.get_options(_config.options)\n\telse:\n\t\t_config.options = options\n\t\t\n\t# We ar running from within the game, so we should not exit, ever.\n\t_config.options.should_exit_on_success = false\n\t_config.options.should_exit = false\n\n\t_gut_runner.get_gut().get_test_collector().clear()\n\t_gut_runner.set_gut_config(_config)\n\t_gut_runner.run_tests()\n\n\nfunc run_selected():\n\tvar sel_item = _ctrls.test_tree.get_selected()\n\tif(sel_item == null):\n\t\treturn\n\n\tvar options = _config_gui.get_options(_config.options)\n\tvar meta = sel_item.get_metadata(0)\n\tif(meta.type == TREE_SCRIPT):\n\t\toptions.selected = meta.script.get_file()\n\t\toptions.inner_class_name = meta.inner_class\n\t\toptions.unit_test_name = meta.test\n\telif(meta.type == TREE_DIR):\n\t\toptions.dirs = [meta.path]\n\t\toptions.include_subdirectories = true\n\t\toptions.selected = ''\n\t\toptions.inner_class_name = ''\n\t\toptions.unit_test_name = ''\n\n\trun_tests(options)\n\n\nfunc load_config_file(path):\n\t_config.load_options(path)\n\t_config.options.selected = ''\n\t_config.options.inner_class_name = ''\n\t_config.options.unit_test_name = ''\n\t_config_gui.load_file(path)\n\n\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n"
  },
  {
    "path": "demo/addons/gut/gui/GutControl.gd.uid",
    "content": "uid://cqlvpwidawld6\n"
  },
  {
    "path": "demo/addons/gut/gui/GutControl.tscn",
    "content": "[gd_scene load_steps=2 format=3 uid=\"uid://4jb53yqktyfg\"]\n\n[ext_resource type=\"Script\" uid=\"uid://cqlvpwidawld6\" path=\"res://addons/gut/gui/GutControl.gd\" id=\"1_eprql\"]\n\n[node name=\"GutControl\" type=\"Control\"]\nlayout_mode = 3\nanchors_preset = 0\noffset_right = 295.0\noffset_bottom = 419.0\nscript = ExtResource(\"1_eprql\")\n\n[node name=\"Bg\" type=\"ColorRect\" parent=\".\"]\nlayout_mode = 1\nanchors_preset = 15\nanchor_right = 1.0\nanchor_bottom = 1.0\ngrow_horizontal = 2\ngrow_vertical = 2\ncolor = Color(0.36, 0.36, 0.36, 1)\n\n[node name=\"VBox\" type=\"VBoxContainer\" parent=\".\"]\nlayout_mode = 1\nanchors_preset = 15\nanchor_right = 1.0\nanchor_bottom = 1.0\ngrow_horizontal = 2\ngrow_vertical = 2\n\n[node name=\"Tabs\" type=\"TabContainer\" parent=\"VBox\"]\nlayout_mode = 2\nsize_flags_vertical = 3\n\n[node name=\"Tests\" type=\"Tree\" parent=\"VBox/Tabs\"]\nlayout_mode = 2\nsize_flags_vertical = 3\nhide_root = true\n\n[node name=\"SettingsScroll\" type=\"ScrollContainer\" parent=\"VBox/Tabs\"]\nvisible = false\nlayout_mode = 2\nsize_flags_vertical = 3\n\n[node name=\"Settings\" type=\"VBoxContainer\" parent=\"VBox/Tabs/SettingsScroll\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n\n[node name=\"Buttons\" type=\"HBoxContainer\" parent=\"VBox\"]\nlayout_mode = 2\n\n[node name=\"RunTests\" type=\"Button\" parent=\"VBox/Buttons\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\ntext = \"Run All\"\n\n[node name=\"RunSelected\" type=\"Button\" parent=\"VBox/Buttons\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\ntext = \"Run Selected\"\n\n[connection signal=\"item_activated\" from=\"VBox/Tabs/Tests\" to=\".\" method=\"_on_tests_item_activated\"]\n[connection signal=\"pressed\" from=\"VBox/Buttons/RunTests\" to=\".\" method=\"_on_run_tests_pressed\"]\n[connection signal=\"pressed\" from=\"VBox/Buttons/RunSelected\" to=\".\" method=\"_on_run_selected_pressed\"]\n"
  },
  {
    "path": "demo/addons/gut/gui/GutEditorWindow.gd",
    "content": "@tool\nextends Window\n\n\nvar GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')\n\n@onready var _chk_always_on_top = $Layout/WinControls/OnTop\n\nvar _bottom_panel = null\nvar _ready_to_go = false\nvar _gut_shortcuts = []\n\nvar gut_plugin = null\nvar interface = null\n\n\nfunc _unhandled_key_input(event: InputEvent) -> void:\n\tif(event is InputEventKey):\n\t\tif(_gut_shortcuts.has(event.as_text_keycode())):\n\t\t\tget_tree().root.push_input(event)\n\n\nfunc _ready() -> void:\n\tvar pref_size = GutEditorGlobals.user_prefs.gut_window_size.value\n\tif(pref_size.x < 0):\n\t\tsize = Vector2(800, 800)\n\telse:\n\t\tsize = pref_size\n\talways_on_top = GutEditorGlobals.user_prefs.gut_window_on_top.value\n\t_chk_always_on_top.button_pressed = always_on_top\n\n\n# --------\n# Events\n# --------\nfunc _on_on_top_toggled(toggled_on: bool) -> void:\n\talways_on_top = toggled_on\n\tGutEditorGlobals.user_prefs.gut_window_on_top.value = toggled_on\n\n\nfunc _on_size_changed() -> void:\n\tif(_ready_to_go):\n\t\tGutEditorGlobals.user_prefs.gut_window_size.value = size\n\n\nfunc _on_close_requested() -> void:\n\tgut_plugin.toggle_windowed()\n\n\n\nfunc _on_vert_layout_pressed() -> void:\n\t_bottom_panel.results_vert_layout()\n\n\nfunc _on_horiz_layout_pressed() -> void:\n\t_bottom_panel.results_horiz_layout()\n\n\n# --------\n# Public\n# --------\nfunc add_gut_panel(panel : Control):\n\t$Layout.add_child(panel)\n\tpanel.size_flags_horizontal = Control.SIZE_EXPAND_FILL\n\tpanel.size_flags_vertical = Control.SIZE_EXPAND_FILL\n\tpanel.visible = true\n\t_bottom_panel = panel\n\t_ready_to_go = true\n\n\tpanel.owner = self\n\n\t# This stunk to figure out.\n\ttheme = interface.get_editor_theme()\n\tvar settings = interface.get_editor_settings()\n\t$ColorRect.color = settings.get_setting(\"interface/theme/base_color\")\n\n\tset_gut_shortcuts(_bottom_panel._ctrls.shortcut_dialog)\n\n\nfunc remove_panel():\n\t$Layout.remove_child(_bottom_panel)\n\t_bottom_panel.owner = null\n\n\nfunc set_gut_shortcuts(shortcuts_dialog):\n\t_gut_shortcuts.clear()\n\tfor btn in shortcuts_dialog.all_buttons:\n\t\t_gut_shortcuts.append(btn.get_input_event().as_text_keycode())\n"
  },
  {
    "path": "demo/addons/gut/gui/GutEditorWindow.gd.uid",
    "content": "uid://crp2af6k4nmf5\n"
  },
  {
    "path": "demo/addons/gut/gui/GutEditorWindow.tscn",
    "content": "[gd_scene load_steps=10 format=3 uid=\"uid://dnnvwlplf1km7\"]\n\n[ext_resource type=\"Script\" uid=\"uid://crp2af6k4nmf5\" path=\"res://addons/gut/gui/GutEditorWindow.gd\" id=\"1_qevl3\"]\n[ext_resource type=\"Texture2D\" uid=\"uid://bdyiwc34kad0n\" path=\"res://addons/gut/images/HSplitContainer.svg\" id=\"2_xw0o2\"]\n[ext_resource type=\"Texture2D\" uid=\"uid://cu7rctvlo721o\" path=\"res://addons/gut/images/VSplitContainer.svg\" id=\"3_fqfwy\"]\n\n[sub_resource type=\"StyleBoxFlat\" id=\"StyleBoxFlat_qevl3\"]\ncontent_margin_left = 8.0\ncontent_margin_top = 8.0\ncontent_margin_right = 8.0\ncontent_margin_bottom = 8.0\nbg_color = Color(0.115499996, 0.132, 0.15949999, 1)\ncorner_detail = 1\nanti_aliasing = false\n\n[sub_resource type=\"StyleBoxFlat\" id=\"StyleBoxFlat_af010\"]\ncontent_margin_left = 8.0\ncontent_margin_top = 12.0\ncontent_margin_right = 8.0\ncontent_margin_bottom = 8.0\nbg_color = Color(0.21, 0.24, 0.29, 1)\nborder_color = Color(0.08399999, 0.095999986, 0.116, 1)\ncorner_radius_top_left = 6\ncorner_radius_top_right = 6\ncorner_radius_bottom_right = 6\ncorner_radius_bottom_left = 6\ncorner_detail = 5\nanti_aliasing = false\n\n[sub_resource type=\"StyleBoxFlat\" id=\"StyleBoxFlat_xw0o2\"]\ncontent_margin_left = 0.0\ncontent_margin_top = 8.0\ncontent_margin_right = 0.0\ncontent_margin_bottom = 0.0\nbg_color = Color(0.21, 0.24, 0.29, 1)\nborder_color = Color(0.08399999, 0.095999986, 0.116, 1)\ncorner_radius_bottom_right = 6\ncorner_radius_bottom_left = 6\ncorner_detail = 5\nanti_aliasing = false\n\n[sub_resource type=\"StyleBoxFlat\" id=\"StyleBoxFlat_fqfwy\"]\ncontent_margin_left = 12.0\ncontent_margin_top = 8.0\ncontent_margin_right = 12.0\ncontent_margin_bottom = 8.0\nbg_color = Color(0.14699998, 0.16799998, 0.203, 1)\ncorner_radius_top_left = 6\ncorner_radius_top_right = 6\ncorner_radius_bottom_right = 6\ncorner_radius_bottom_left = 6\ncorner_detail = 5\nanti_aliasing = false\n\n[sub_resource type=\"Theme\" id=\"Theme_fqfwy\"]\nEditor/colors/accent_color = Color(0.44, 0.73, 0.98, 1)\nEditor/colors/background = Color(0.115499996, 0.132, 0.15949999, 1)\nEditor/colors/base_color = Color(0.21, 0.24, 0.29, 1)\nEditorStyles/styles/Background = SubResource(\"StyleBoxFlat_qevl3\")\nEditorStyles/styles/BottomPanel = SubResource(\"StyleBoxFlat_af010\")\nEditorStyles/styles/Content = SubResource(\"StyleBoxFlat_xw0o2\")\nPanel/styles/panel = SubResource(\"StyleBoxFlat_fqfwy\")\n\n[sub_resource type=\"ButtonGroup\" id=\"ButtonGroup_qevl3\"]\n\n[node name=\"GutEditorWindow\" type=\"Window\"]\noversampling_override = 1.0\ntitle = \"GUT\"\nposition = Vector2i(0, 36)\nsize = Vector2i(800, 800)\nmin_size = Vector2i(800, 600)\nscript = ExtResource(\"1_qevl3\")\n\n[node name=\"ColorRect\" type=\"ColorRect\" parent=\".\"]\nanchors_preset = 15\nanchor_right = 1.0\nanchor_bottom = 1.0\ngrow_horizontal = 2\ngrow_vertical = 2\ntheme = SubResource(\"Theme_fqfwy\")\ncolor = Color(0.18717614, 0.18717614, 0.18717614, 1)\n\n[node name=\"Layout\" type=\"VBoxContainer\" parent=\".\"]\nanchors_preset = 15\nanchor_right = 1.0\nanchor_bottom = 1.0\ngrow_horizontal = 2\ngrow_vertical = 2\n\n[node name=\"WinControls\" type=\"HBoxContainer\" parent=\"Layout\"]\nlayout_mode = 2\n\n[node name=\"MenuBar\" type=\"MenuBar\" parent=\"Layout/WinControls\"]\ncustom_minimum_size = Vector2(300, 0)\nlayout_mode = 2\nsize_flags_horizontal = 3\nflat = true\nprefer_global_menu = false\n\n[node name=\"CenterContainer\" type=\"CenterContainer\" parent=\"Layout/WinControls\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"OnTop\" type=\"CheckButton\" parent=\"Layout/WinControls\"]\nlayout_mode = 2\ntext = \"Always on Top\"\n\n[node name=\"HorizLayout\" type=\"Button\" parent=\"Layout/WinControls\"]\ntexture_filter = 1\nlayout_mode = 2\ntoggle_mode = true\nbutton_pressed = true\nbutton_group = SubResource(\"ButtonGroup_qevl3\")\nicon = ExtResource(\"2_xw0o2\")\nicon_alignment = 1\n\n[node name=\"VertLayout\" type=\"Button\" parent=\"Layout/WinControls\"]\nlayout_mode = 2\ntoggle_mode = true\nbutton_group = SubResource(\"ButtonGroup_qevl3\")\nicon = ExtResource(\"3_fqfwy\")\n\n[connection signal=\"close_requested\" from=\".\" to=\".\" method=\"_on_close_requested\"]\n[connection signal=\"size_changed\" from=\".\" to=\".\" method=\"_on_size_changed\"]\n[connection signal=\"toggled\" from=\"Layout/WinControls/OnTop\" to=\".\" method=\"_on_on_top_toggled\"]\n[connection signal=\"pressed\" from=\"Layout/WinControls/HorizLayout\" to=\".\" method=\"_on_horiz_layout_pressed\"]\n[connection signal=\"pressed\" from=\"Layout/WinControls/VertLayout\" to=\".\" method=\"_on_vert_layout_pressed\"]\n"
  },
  {
    "path": "demo/addons/gut/gui/GutLogo.tscn",
    "content": "[gd_scene load_steps=4 format=3 uid=\"uid://bjkn8mhx2fmt1\"]\n\n[ext_resource type=\"Script\" uid=\"uid://b8lvgepb64m8t\" path=\"res://addons/gut/gui/gut_logo.gd\" id=\"1_ba6lh\"]\n[ext_resource type=\"Texture2D\" uid=\"uid://ckonsqa7kg74h\" path=\"res://addons/gut/images/GutIconV2_base.png\" id=\"2_ba6lh\"]\n[ext_resource type=\"Texture2D\" uid=\"uid://beltjeifto36x\" path=\"res://addons/gut/images/eyey.png\" id=\"3_rc8fb\"]\n\n[node name=\"Logo\" type=\"Node2D\"]\nscript = ExtResource(\"1_ba6lh\")\n\n[node name=\"BaseLogo\" type=\"Sprite2D\" parent=\".\"]\nscale = Vector2(0.5, 0.5)\ntexture = ExtResource(\"2_ba6lh\")\n\n[node name=\"LeftEye\" type=\"Sprite2D\" parent=\"BaseLogo\"]\nvisible = false\nposition = Vector2(-238, 16)\ntexture = ExtResource(\"3_rc8fb\")\n\n[node name=\"RightEye\" type=\"Sprite2D\" parent=\"BaseLogo\"]\nvisible = false\nposition = Vector2(239, 16)\ntexture = ExtResource(\"3_rc8fb\")\n\n[node name=\"ResetTimer\" type=\"Timer\" parent=\".\"]\nwait_time = 5.0\none_shot = true\n\n[node name=\"FaceButton\" type=\"Button\" parent=\".\"]\nmodulate = Color(1, 1, 1, 0)\noffset_left = -141.0\noffset_top = -113.0\noffset_right = 140.0\noffset_bottom = 175.0\n\n[connection signal=\"timeout\" from=\"ResetTimer\" to=\".\" method=\"_on_reset_timer_timeout\"]\n[connection signal=\"pressed\" from=\"FaceButton\" to=\".\" method=\"_on_face_button_pressed\"]\n"
  },
  {
    "path": "demo/addons/gut/gui/GutRunner.gd",
    "content": "# ##############################################################################\n# This class joins together GUT, GUT Gui, GutConfig and is THE way to kick off a\n# run of a test suite.\n#\n# This creates its own instance of gut.gd that it manages.  You can set the\n# gut.gd instance if you need to for testing.\n#\n# Set gut_config to an instance of a configured gut_config.gd instance prior to\n# running tests.\n#\n# This will create a GUI and wire it up and apply gut_config.gd options.\n#\n# Running tests:  Call run_tests\n# ##############################################################################\nextends Node2D\n\nconst EXIT_OK = 0\nconst EXIT_ERROR = 1\n\nvar Gut = load('res://addons/gut/gut.gd')\nvar ResultExporter = load('res://addons/gut/result_exporter.gd')\nvar GutConfig = load('res://addons/gut/gut_config.gd')\n\nvar runner_json_path = null\nvar result_bbcode_path = null\nvar result_json_path = null\n\nvar lgr = GutUtils.get_logger()\nvar gut_config = null\n\nvar error_tracker = GutUtils.get_error_tracker()\n\nvar _hid_gut = null;\n# Lazy loaded gut instance.  Settable for testing purposes.\nvar gut = _hid_gut :\n\tget:\n\t\tif(_hid_gut == null):\n\t\t\t_hid_gut = Gut.new(lgr)\n\t\t\t_hid_gut.error_tracker = error_tracker\n\t\treturn _hid_gut\n\tset(val):\n\t\t_hid_gut = val\n\nvar _wrote_results = false\nvar _ran_from_editor = false\n\n@onready var _gut_layer = $GutLayer\n@onready var _gui = $GutLayer/GutScene\n\n\nfunc _ready():\n\tGutUtils.WarningsManager.apply_warnings_dictionary(\n\t\tGutUtils.warnings_at_start)\n\n\nfunc _exit_tree():\n\tif(!_wrote_results and _ran_from_editor):\n\t\t_write_results_for_gut_panel()\n\n\nfunc _setup_gui(show_gui):\n\tif(show_gui):\n\t\t_gui.gut = gut\n\t\tvar printer = gut.logger.get_printer('gui')\n\t\tprinter.set_textbox(_gui.get_textbox())\n\telse:\n\t\tgut.logger.disable_printer('gui', true)\n\t\t_gui.visible = false\n\n\tvar opts = gut_config.options\n\t_gui.set_font_size(opts.font_size)\n\t_gui.set_font(opts.font_name)\n\tif(opts.font_color != null and opts.font_color.is_valid_html_color()):\n\t\t_gui.set_default_font_color(Color(opts.font_color))\n\tif(opts.background_color != null and opts.background_color.is_valid_html_color()):\n\t\t_gui.set_background_color(Color(opts.background_color))\n\n\t_gui.set_opacity(min(1.0, float(opts.opacity) / 100))\n\t_gui.use_compact_mode(opts.compact_mode)\n\n\nfunc _write_results_for_gut_panel():\n\tvar content = _gui.get_textbox().get_parsed_text() #_gut.logger.get_gui_bbcode()\n\tvar f = FileAccess.open(result_bbcode_path, FileAccess.WRITE)\n\tif(f != null):\n\t\tf.store_string(content)\n\t\tf = null # closes file\n\telse:\n\t\tpush_error('Could not save bbcode, result = ', FileAccess.get_open_error())\n\n\tvar exporter = ResultExporter.new()\n\t# TODO this should be checked and _wrote_results should maybe not be set, or\n\t# maybe we do not care.  Whichever, it should be clear.\n\tvar _f_result = exporter.write_json_file(gut, result_json_path)\n\t_wrote_results = true\n\n\nfunc _handle_quit(should_exit, should_exit_on_success, override_exit_code=EXIT_OK):\n\tvar quitting_time = should_exit or \\\n\t\t(should_exit_on_success and gut.get_fail_count() == 0)\n\n\tif(!quitting_time):\n\t\tif(should_exit_on_success):\n\t\t\tlgr.log(\"There are failing tests, exit manually.\")\n\t\t_gui.use_compact_mode(false)\n\t\treturn\n\n\t# For some reason, tests fail asserting that quit was called with 0 if we\n\t# do not do this, but everything is defaulted so I don't know why it gets\n\t# null.\n\tvar exit_code = GutUtils.nvl(override_exit_code, EXIT_OK)\n\n\tif(gut.get_fail_count() > 0):\n\t\texit_code = EXIT_ERROR\n\n\t# Overwrite the exit code with the post_script's exit code if it is set\n\tvar post_hook_inst = gut.get_post_run_script_instance()\n\tif(post_hook_inst != null and post_hook_inst.get_exit_code() != null):\n\t\texit_code = post_hook_inst.get_exit_code()\n\n\tquit(exit_code)\n\n\nfunc _end_run(override_exit_code=EXIT_OK):\n\tif(_ran_from_editor):\n\t\t_write_results_for_gut_panel()\n\n\tGutErrorTracker.deregister_logger(error_tracker)\n\n\t_handle_quit(gut_config.options.should_exit,\n\t\tgut_config.options.should_exit_on_success,\n\t\toverride_exit_code)\n\n\n# -------------\n# Events\n# -------------\nfunc _on_tests_finished():\n\t_end_run()\n\n\n# -------------\n# Public\n# -------------\n# For internal use only, but still public.  Consider it \"protected\" and you\n# don't have my permission to call this, unless \"you\" is \"me\".\nfunc run_from_editor():\n\t_ran_from_editor = true\n\tvar GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')\n\trunner_json_path = GutUtils.nvl(runner_json_path, GutEditorGlobals.editor_run_gut_config_path)\n\tresult_bbcode_path = GutUtils.nvl(result_bbcode_path, GutEditorGlobals.editor_run_bbcode_results_path)\n\tresult_json_path = GutUtils.nvl(result_json_path, GutEditorGlobals.editor_run_json_results_path)\n\n\tif(gut_config == null):\n\t\tgut_config = GutConfig.new()\n\t\tgut_config.load_options(runner_json_path)\n\n\tcall_deferred('run_tests')\n\n\nfunc run_tests(show_gui=true):\n\t_setup_gui(show_gui)\n\n\tif(gut_config.options.dirs.size() + gut_config.options.tests.size() == 0):\n\t\tvar err_text = \"You do not have any directories configured, so GUT \" + \\\n\t\t\t\"doesn't know where to find the tests.  Tell GUT where to find the \" + \\\n\t\t\t\"tests and GUT shall run the tests.\"\n\t\tlgr.error(err_text)\n\t\tpush_error(err_text)\n\t\t_end_run(EXIT_ERROR)\n\t\treturn\n\n\tvar install_check_text = GutUtils.make_install_check_text()\n\tif(install_check_text != GutUtils.INSTALL_OK_TEXT):\n\t\tprint(\"\\n\\n\", GutUtils.version_numbers.get_version_text())\n\t\tlgr.error(install_check_text)\n\t\tpush_error(install_check_text)\n\t\t_end_run(EXIT_ERROR)\n\t\treturn\n\n\tgut.add_children_to = self\n\tif(gut.get_parent() == null):\n\t\tif(gut_config.options.gut_on_top):\n\t\t\t_gut_layer.add_child(gut)\n\t\telse:\n\t\t\tadd_child(gut)\n\t\n\tif(!gut.end_run.is_connected(_on_tests_finished)):\n\t\tgut.end_run.connect(_on_tests_finished)\n\n\tgut_config.apply_options(gut)\n\tvar run_rest_of_scripts = gut_config.options.unit_test_name == ''\n\tGutErrorTracker.register_logger(error_tracker)\n\tgut.test_scripts(run_rest_of_scripts)\n\n\nfunc set_gut_config(which):\n\tgut_config = which\n\n\n# for backwards compatibility\nfunc get_gut():\n\treturn gut\n\n\nfunc quit(exit_code):\n\t# Sometimes quitting takes a few seconds.  This gives some indicator\n\t# of what is going on.\n\t_gui.set_title(\"Exiting\")\n\n\tawait get_tree().process_frame\n\n\tlgr.info(str('Exiting with code ', exit_code))\n\tget_tree().quit(exit_code)\n\n\n\n\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n"
  },
  {
    "path": "demo/addons/gut/gui/GutRunner.gd.uid",
    "content": "uid://eg8k46gd42a4\n"
  },
  {
    "path": "demo/addons/gut/gui/GutRunner.tscn",
    "content": "[gd_scene load_steps=3 format=3 uid=\"uid://bqy3ikt6vu4b5\"]\n\n[ext_resource type=\"Script\" uid=\"uid://eg8k46gd42a4\" path=\"res://addons/gut/gui/GutRunner.gd\" id=\"1\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://m28heqtswbuq\" path=\"res://addons/gut/GutScene.tscn\" id=\"2_6ruxb\"]\n\n[node name=\"GutRunner\" type=\"Node2D\"]\nscript = ExtResource(\"1\")\n\n[node name=\"GutLayer\" type=\"CanvasLayer\" parent=\".\"]\nlayer = 128\n\n[node name=\"GutScene\" parent=\"GutLayer\" instance=ExtResource(\"2_6ruxb\")]\n"
  },
  {
    "path": "demo/addons/gut/gui/GutSceneTheme.tres",
    "content": "[gd_resource type=\"Theme\" load_steps=2 format=3 uid=\"uid://cstkhwkpajvqu\"]\n\n[ext_resource type=\"FontFile\" uid=\"uid://c6c7gnx36opr0\" path=\"res://addons/gut/fonts/AnonymousPro-Regular.ttf\" id=\"1_df57p\"]\n\n[resource]\ndefault_font = ExtResource(\"1_df57p\")\nLabel/fonts/font = ExtResource(\"1_df57p\")\n"
  },
  {
    "path": "demo/addons/gut/gui/MinGui.tscn",
    "content": "[gd_scene load_steps=5 format=3 uid=\"uid://cnqqdfsn80ise\"]\n\n[ext_resource type=\"Theme\" uid=\"uid://cstkhwkpajvqu\" path=\"res://addons/gut/gui/GutSceneTheme.tres\" id=\"1_farmq\"]\n[ext_resource type=\"FontFile\" uid=\"uid://bnh0lslf4yh87\" path=\"res://addons/gut/fonts/CourierPrime-Regular.ttf\" id=\"2_a2e2l\"]\n[ext_resource type=\"Script\" uid=\"uid://blvhsbnsvfyow\" path=\"res://addons/gut/gui/gut_gui.gd\" id=\"2_eokrf\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://bvrqqgjpyouse\" path=\"res://addons/gut/gui/ResizeHandle.tscn\" id=\"4_xrhva\"]\n\n[node name=\"Min\" type=\"Panel\"]\nclip_contents = true\ncustom_minimum_size = Vector2(280, 145)\noffset_right = 280.0\noffset_bottom = 145.0\ntheme = ExtResource(\"1_farmq\")\nscript = ExtResource(\"2_eokrf\")\n\n[node name=\"MainBox\" type=\"VBoxContainer\" parent=\".\"]\nlayout_mode = 1\nanchors_preset = 15\nanchor_right = 1.0\nanchor_bottom = 1.0\ngrow_horizontal = 2\ngrow_vertical = 2\nmetadata/_edit_layout_mode = 1\n\n[node name=\"TitleBar\" type=\"Panel\" parent=\"MainBox\"]\ncustom_minimum_size = Vector2(0, 25)\nlayout_mode = 2\n\n[node name=\"TitleBox\" type=\"HBoxContainer\" parent=\"MainBox/TitleBar\"]\nlayout_mode = 0\nanchor_right = 1.0\nanchor_bottom = 1.0\noffset_top = 2.0\noffset_bottom = 3.0\ngrow_horizontal = 2\ngrow_vertical = 2\nmetadata/_edit_layout_mode = 1\n\n[node name=\"Spacer1\" type=\"CenterContainer\" parent=\"MainBox/TitleBar/TitleBox\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"Title\" type=\"Label\" parent=\"MainBox/TitleBar/TitleBox\"]\nlayout_mode = 2\ntext = \"Title\"\n\n[node name=\"Spacer2\" type=\"CenterContainer\" parent=\"MainBox/TitleBar/TitleBox\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"TimeLabel\" type=\"Label\" parent=\"MainBox/TitleBar/TitleBox\"]\nlayout_mode = 2\ntext = \"0.000s\"\n\n[node name=\"Body\" type=\"HBoxContainer\" parent=\"MainBox\"]\nlayout_mode = 2\nsize_flags_vertical = 3\n\n[node name=\"LeftMargin\" type=\"CenterContainer\" parent=\"MainBox/Body\"]\ncustom_minimum_size = Vector2(5, 0)\nlayout_mode = 2\n\n[node name=\"BodyRows\" type=\"VBoxContainer\" parent=\"MainBox/Body\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"ProgressBars\" type=\"HBoxContainer\" parent=\"MainBox/Body/BodyRows\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"HBoxContainer\" type=\"HBoxContainer\" parent=\"MainBox/Body/BodyRows/ProgressBars\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"Label\" type=\"Label\" parent=\"MainBox/Body/BodyRows/ProgressBars/HBoxContainer\"]\nlayout_mode = 2\ntext = \"T:\"\n\n[node name=\"ProgressTest\" type=\"ProgressBar\" parent=\"MainBox/Body/BodyRows/ProgressBars/HBoxContainer\"]\ncustom_minimum_size = Vector2(100, 0)\nlayout_mode = 2\nsize_flags_horizontal = 3\nvalue = 25.0\n\n[node name=\"HBoxContainer2\" type=\"HBoxContainer\" parent=\"MainBox/Body/BodyRows/ProgressBars\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"Label\" type=\"Label\" parent=\"MainBox/Body/BodyRows/ProgressBars/HBoxContainer2\"]\nlayout_mode = 2\ntext = \"S:\"\n\n[node name=\"ProgressScript\" type=\"ProgressBar\" parent=\"MainBox/Body/BodyRows/ProgressBars/HBoxContainer2\"]\ncustom_minimum_size = Vector2(100, 0)\nlayout_mode = 2\nsize_flags_horizontal = 3\nvalue = 75.0\n\n[node name=\"PathDisplay\" type=\"VBoxContainer\" parent=\"MainBox/Body/BodyRows\"]\nclip_contents = true\nlayout_mode = 2\nsize_flags_vertical = 3\n\n[node name=\"Path\" type=\"Label\" parent=\"MainBox/Body/BodyRows/PathDisplay\"]\nlayout_mode = 2\ntheme_override_fonts/font = ExtResource(\"2_a2e2l\")\ntheme_override_font_sizes/font_size = 14\ntext = \"res://test/integration/whatever\"\nclip_text = true\ntext_overrun_behavior = 3\n\n[node name=\"HBoxContainer\" type=\"HBoxContainer\" parent=\"MainBox/Body/BodyRows/PathDisplay\"]\nclip_contents = true\nlayout_mode = 2\n\n[node name=\"S3\" type=\"CenterContainer\" parent=\"MainBox/Body/BodyRows/PathDisplay/HBoxContainer\"]\ncustom_minimum_size = Vector2(5, 0)\nlayout_mode = 2\n\n[node name=\"File\" type=\"Label\" parent=\"MainBox/Body/BodyRows/PathDisplay/HBoxContainer\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\ntheme_override_fonts/font = ExtResource(\"2_a2e2l\")\ntheme_override_font_sizes/font_size = 14\ntext = \"test_this_thing.gd\"\ntext_overrun_behavior = 3\n\n[node name=\"Footer\" type=\"HBoxContainer\" parent=\"MainBox/Body/BodyRows\"]\nlayout_mode = 2\n\n[node name=\"HandleLeft\" parent=\"MainBox/Body/BodyRows/Footer\" node_paths=PackedStringArray(\"resize_control\") instance=ExtResource(\"4_xrhva\")]\nlayout_mode = 2\norientation = 0\nresize_control = NodePath(\"../../../../..\")\nvertical_resize = false\n\n[node name=\"SwitchModes\" type=\"Button\" parent=\"MainBox/Body/BodyRows/Footer\"]\nlayout_mode = 2\ntext = \"Expand\"\n\n[node name=\"CenterContainer\" type=\"CenterContainer\" parent=\"MainBox/Body/BodyRows/Footer\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"Continue\" type=\"Button\" parent=\"MainBox/Body/BodyRows/Footer\"]\nlayout_mode = 2\ntext = \"Continue\n\"\n\n[node name=\"HandleRight\" parent=\"MainBox/Body/BodyRows/Footer\" node_paths=PackedStringArray(\"resize_control\") instance=ExtResource(\"4_xrhva\")]\nlayout_mode = 2\nresize_control = NodePath(\"../../../../..\")\nvertical_resize = false\n\n[node name=\"RightMargin\" type=\"CenterContainer\" parent=\"MainBox/Body\"]\ncustom_minimum_size = Vector2(5, 0)\nlayout_mode = 2\n\n[node name=\"CenterContainer\" type=\"CenterContainer\" parent=\"MainBox\"]\ncustom_minimum_size = Vector2(2.08165e-12, 2)\nlayout_mode = 2\n"
  },
  {
    "path": "demo/addons/gut/gui/NormalGui.tscn",
    "content": "[gd_scene load_steps=5 format=3 uid=\"uid://duxblir3vu8x7\"]\n\n[ext_resource type=\"Theme\" uid=\"uid://cstkhwkpajvqu\" path=\"res://addons/gut/gui/GutSceneTheme.tres\" id=\"1_5hlsm\"]\n[ext_resource type=\"Script\" uid=\"uid://blvhsbnsvfyow\" path=\"res://addons/gut/gui/gut_gui.gd\" id=\"2_fue6q\"]\n[ext_resource type=\"FontFile\" uid=\"uid://bnh0lslf4yh87\" path=\"res://addons/gut/fonts/CourierPrime-Regular.ttf\" id=\"2_u5uc1\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://bvrqqgjpyouse\" path=\"res://addons/gut/gui/ResizeHandle.tscn\" id=\"4_2r8a8\"]\n\n[node name=\"Large\" type=\"Panel\"]\ncustom_minimum_size = Vector2(500, 150)\noffset_right = 632.0\noffset_bottom = 260.0\ntheme = ExtResource(\"1_5hlsm\")\nscript = ExtResource(\"2_fue6q\")\n\n[node name=\"MainBox\" type=\"VBoxContainer\" parent=\".\"]\nlayout_mode = 0\nanchor_right = 1.0\nanchor_bottom = 1.0\ngrow_horizontal = 2\ngrow_vertical = 2\nmetadata/_edit_layout_mode = 1\n\n[node name=\"TitleBar\" type=\"Panel\" parent=\"MainBox\"]\ncustom_minimum_size = Vector2(0, 25)\nlayout_mode = 2\n\n[node name=\"TitleBox\" type=\"HBoxContainer\" parent=\"MainBox/TitleBar\"]\nlayout_mode = 0\nanchor_right = 1.0\nanchor_bottom = 1.0\noffset_top = 2.0\noffset_bottom = 3.0\ngrow_horizontal = 2\ngrow_vertical = 2\nmetadata/_edit_layout_mode = 1\n\n[node name=\"Spacer1\" type=\"CenterContainer\" parent=\"MainBox/TitleBar/TitleBox\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"Title\" type=\"Label\" parent=\"MainBox/TitleBar/TitleBox\"]\nlayout_mode = 2\ntext = \"Title\"\n\n[node name=\"Spacer2\" type=\"CenterContainer\" parent=\"MainBox/TitleBar/TitleBox\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"TimeLabel\" type=\"Label\" parent=\"MainBox/TitleBar/TitleBox\"]\ncustom_minimum_size = Vector2(90, 0)\nlayout_mode = 2\ntext = \"999.999s\"\n\n[node name=\"HBoxContainer\" type=\"HBoxContainer\" parent=\"MainBox\"]\nlayout_mode = 2\nsize_flags_vertical = 3\n\n[node name=\"VBoxContainer\" type=\"VBoxContainer\" parent=\"MainBox/HBoxContainer\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"OutputBG\" type=\"ColorRect\" parent=\"MainBox/HBoxContainer/VBoxContainer\"]\nlayout_mode = 2\nsize_flags_vertical = 3\ncolor = Color(0.0745098, 0.0705882, 0.0784314, 1)\nmetadata/_edit_layout_mode = 1\n\n[node name=\"HBoxContainer\" type=\"HBoxContainer\" parent=\"MainBox/HBoxContainer/VBoxContainer/OutputBG\"]\nlayout_mode = 0\nanchor_right = 1.0\nanchor_bottom = 1.0\ngrow_horizontal = 2\ngrow_vertical = 2\n\n[node name=\"S2\" type=\"CenterContainer\" parent=\"MainBox/HBoxContainer/VBoxContainer/OutputBG/HBoxContainer\"]\ncustom_minimum_size = Vector2(5, 0)\nlayout_mode = 2\n\n[node name=\"TestOutput\" type=\"RichTextLabel\" parent=\"MainBox/HBoxContainer/VBoxContainer/OutputBG/HBoxContainer\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\nfocus_mode = 2\nbbcode_enabled = true\nscroll_following = true\nautowrap_mode = 0\nselection_enabled = true\n\n[node name=\"S1\" type=\"CenterContainer\" parent=\"MainBox/HBoxContainer/VBoxContainer/OutputBG/HBoxContainer\"]\ncustom_minimum_size = Vector2(5, 0)\nlayout_mode = 2\n\n[node name=\"ControlBox\" type=\"HBoxContainer\" parent=\"MainBox/HBoxContainer/VBoxContainer\"]\nlayout_mode = 2\n\n[node name=\"S1\" type=\"CenterContainer\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox\"]\ncustom_minimum_size = Vector2(5, 0)\nlayout_mode = 2\n\n[node name=\"ProgressBars\" type=\"VBoxContainer\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox\"]\ncustom_minimum_size = Vector2(2.08165e-12, 2.08165e-12)\nlayout_mode = 2\n\n[node name=\"TestBox\" type=\"HBoxContainer\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars\"]\nlayout_mode = 2\n\n[node name=\"Label\" type=\"Label\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars/TestBox\"]\ncustom_minimum_size = Vector2(60, 0)\nlayout_mode = 2\nsize_flags_horizontal = 3\ntext = \"Tests\"\n\n[node name=\"ProgressTest\" type=\"ProgressBar\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars/TestBox\"]\ncustom_minimum_size = Vector2(100, 0)\nlayout_mode = 2\nvalue = 25.0\n\n[node name=\"ScriptBox\" type=\"HBoxContainer\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars\"]\nlayout_mode = 2\n\n[node name=\"Label\" type=\"Label\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars/ScriptBox\"]\ncustom_minimum_size = Vector2(60, 0)\nlayout_mode = 2\nsize_flags_horizontal = 3\ntext = \"Scripts\"\n\n[node name=\"ProgressScript\" type=\"ProgressBar\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars/ScriptBox\"]\ncustom_minimum_size = Vector2(100, 0)\nlayout_mode = 2\nvalue = 75.0\n\n[node name=\"PathDisplay\" type=\"VBoxContainer\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n\n[node name=\"Path\" type=\"Label\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox/PathDisplay\"]\nlayout_mode = 2\nsize_flags_vertical = 6\ntheme_override_fonts/font = ExtResource(\"2_u5uc1\")\ntheme_override_font_sizes/font_size = 14\ntext = \"res://test/integration/whatever\"\ntext_overrun_behavior = 3\n\n[node name=\"HBoxContainer\" type=\"HBoxContainer\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox/PathDisplay\"]\nlayout_mode = 2\nsize_flags_vertical = 3\n\n[node name=\"S3\" type=\"CenterContainer\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox/PathDisplay/HBoxContainer\"]\ncustom_minimum_size = Vector2(5, 0)\nlayout_mode = 2\n\n[node name=\"File\" type=\"Label\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox/PathDisplay/HBoxContainer\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\ntheme_override_fonts/font = ExtResource(\"2_u5uc1\")\ntheme_override_font_sizes/font_size = 14\ntext = \"test_this_thing.gd\"\ntext_overrun_behavior = 3\n\n[node name=\"Buttons\" type=\"VBoxContainer\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox\"]\nlayout_mode = 2\n\n[node name=\"Continue\" type=\"Button\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox/Buttons\"]\nlayout_mode = 2\nsize_flags_vertical = 4\ntext = \"Continue\n\"\n\n[node name=\"WordWrap\" type=\"CheckButton\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox/Buttons\"]\nlayout_mode = 2\ntext = \"Word Wrap\"\n\n[node name=\"S3\" type=\"CenterContainer\" parent=\"MainBox/HBoxContainer/VBoxContainer/ControlBox\"]\ncustom_minimum_size = Vector2(5, 0)\nlayout_mode = 2\n\n[node name=\"BottomPad\" type=\"CenterContainer\" parent=\"MainBox\"]\ncustom_minimum_size = Vector2(0, 5)\nlayout_mode = 2\n\n[node name=\"Footer\" type=\"HBoxContainer\" parent=\"MainBox\"]\nlayout_mode = 2\n\n[node name=\"SidePad1\" type=\"CenterContainer\" parent=\"MainBox/Footer\"]\ncustom_minimum_size = Vector2(2, 2.08165e-12)\nlayout_mode = 2\n\n[node name=\"ResizeHandle3\" parent=\"MainBox/Footer\" node_paths=PackedStringArray(\"resize_control\") instance=ExtResource(\"4_2r8a8\")]\ncustom_minimum_size = Vector2(25, 25)\nlayout_mode = 2\norientation = 0\nresize_control = NodePath(\"../../..\")\n\n[node name=\"SwitchModes\" type=\"Button\" parent=\"MainBox/Footer\"]\nlayout_mode = 2\ntext = \"Compact\n\"\n\n[node name=\"CenterContainer\" type=\"CenterContainer\" parent=\"MainBox/Footer\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"ResizeHandle2\" parent=\"MainBox/Footer\" node_paths=PackedStringArray(\"resize_control\") instance=ExtResource(\"4_2r8a8\")]\ncustom_minimum_size = Vector2(25, 25)\nlayout_mode = 2\nresize_control = NodePath(\"../../..\")\n\n[node name=\"SidePad2\" type=\"CenterContainer\" parent=\"MainBox/Footer\"]\ncustom_minimum_size = Vector2(2, 2.08165e-12)\nlayout_mode = 2\n\n[node name=\"BottomPad2\" type=\"CenterContainer\" parent=\"MainBox\"]\ncustom_minimum_size = Vector2(2.08165e-12, 2)\nlayout_mode = 2\n"
  },
  {
    "path": "demo/addons/gut/gui/OutputText.gd",
    "content": "@tool\nextends VBoxContainer\n\nvar GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')\nvar PanelControls = load('res://addons/gut/gui/panel_controls.gd')\n\n# ##############################################################################\n# Keeps search results from the TextEdit\n# ##############################################################################\nclass TextEditSearcher:\n\tvar te : TextEdit\n\tvar _last_term = ''\n\tvar _last_pos = Vector2(-1, -1)\n\tvar _ignore_caret_change = false\n\n\tfunc set_text_edit(which):\n\t\tte = which\n\t\tte.caret_changed.connect(_on_caret_changed)\n\n\n\tfunc _on_caret_changed():\n\t\tif(_ignore_caret_change):\n\t\t\t_ignore_caret_change = false\n\t\telse:\n\t\t\t_last_pos = _get_caret();\n\n\n\tfunc _get_caret():\n\t\treturn Vector2(te.get_caret_column(), te.get_caret_line())\n\n\n\tfunc _set_caret_and_sel(pos, len):\n\t\tte.set_caret_line(pos.y)\n\t\tte.set_caret_column(pos.x)\n\t\tif(len > 0):\n\t\t\tte.select(pos.y, pos.x, pos.y, pos.x + len)\n\n\n\tfunc _find(term, search_flags):\n\t\tvar pos = _get_caret()\n\t\tif(term == _last_term):\n\t\t\tif(search_flags == 0):\n\t\t\t\tpos = _last_pos\n\t\t\t\tpos.x += 1\n\t\t\telse:\n\t\t\t\tpos = _last_pos\n\t\t\t\tpos.x -= 1\n\n\t\tvar result = te.search(term, search_flags, pos.y, pos.x)\n#\t\tprint('searching from ', pos, ' for \"', term, '\" = ', result)\n\t\tif(result.y != -1):\n\t\t\t_ignore_caret_change = true\n\t\t\t_set_caret_and_sel(result, term.length())\n\t\t\t_last_pos = result\n\n\t\t_last_term = term\n\n\tfunc find_next(term):\n\t\t_find(term, 0)\n\n\tfunc find_prev(term):\n\t\t_find(term, te.SEARCH_BACKWARDS)\n\n\n# ##############################################################################\n# Start OutputText control code\n# ##############################################################################\n@onready var _ctrls = {\n\toutput = $Output,\n\tsettings_bar = $Settings,\n\tuse_colors = $Settings/UseColors,\n\tword_wrap = $Settings/WordWrap,\n\n\tcopy_button = $Toolbar/CopyButton,\n\tclear_button = $Toolbar/ClearButton,\n\tshow_search = $Toolbar/ShowSearch,\n\tcaret_position = $Toolbar/LblPosition,\n\n\tsearch_bar = {\n\t\tbar = $Search,\n\t\tsearch_term = $Search/SearchTerm,\n\t}\n}\n\nvar _sr = TextEditSearcher.new()\nvar _highlighter : CodeHighlighter\nvar _font_name = null\nvar _user_prefs = GutEditorGlobals.user_prefs\nvar _font_name_pctrl = null\nvar _font_size_pctrl = null\n\nvar keywords = [\n\t['Failed', Color.RED],\n\t['Passed', Color.GREEN],\n\t['Pending', Color.YELLOW],\n\t['Risky', Color.YELLOW],\n\t['Orphans', Color.YELLOW],\n\t['WARNING', Color.YELLOW],\n\t['ERROR', Color.RED],\n\t['ExpectedError', Color.LIGHT_BLUE],\n]\n\n\n# Automatically used when running the OutputText scene from the editor.  Changes\n# to this method only affect test-running the control through the editor.\nfunc _test_running_setup():\n\t_ctrls.use_colors.text = 'use colors'\n\t_ctrls.show_search.text = 'search'\n\t_ctrls.word_wrap.text = 'ww'\n\n\tset_all_fonts(\"CourierPrime\")\n\tset_font_size(30)\n\n\t_ctrls.output.queue_redraw()\n\tload_file('user://.gut_editor.bbcode')\n\tawait get_tree().process_frame\n\n\tshow_search(true)\n\t_ctrls.output.set_caret_line(0)\n\t_ctrls.output.scroll_vertical = 0\n\t_ctrls.output.caret_changed.connect(_on_caret_changed)\n\n\nfunc _ready():\n\tif(get_parent() is SubViewport):\n\t\treturn\n\n\t_sr.set_text_edit(_ctrls.output)\n\t_ctrls.use_colors.icon = get_theme_icon('RichTextEffect', 'EditorIcons')\n\t_ctrls.show_search.icon = get_theme_icon('Search', 'EditorIcons')\n\t_ctrls.word_wrap.icon = get_theme_icon('Loop', 'EditorIcons')\n\n\t_setup_colors()\n\t_ctrls.use_colors.button_pressed = true\n\t_use_highlighting(true)\n\n\tif(get_parent() == get_tree().root):\n\t\t_test_running_setup()\n\n\t_ctrls.settings_bar.visible = false\n\t_add_other_ctrls()\n\n\nfunc _add_other_ctrls():\n\tvar fname = GutUtils.gut_fonts.DEFAULT_CUSTOM_FONT_NAME\n\tif(_user_prefs != null):\n\t\tfname = _user_prefs.output_font_name.value\n\t_font_name_pctrl = PanelControls.SelectControl.new('Font', fname, GutUtils.avail_fonts,\n\t\t\"The font, you know, for the text below.  Change it, see what it does.\")\n\t_font_name_pctrl.changed.connect(_on_font_name_changed)\n\t_font_name_pctrl.label.size_flags_horizontal = SIZE_SHRINK_BEGIN\n\t_ctrls.settings_bar.add_child(_font_name_pctrl)\n\tset_all_fonts(fname)\n\n\tvar fsize = 30\n\tif(_user_prefs != null):\n\t\tfsize = _user_prefs.output_font_size.value\n\t_font_size_pctrl = PanelControls.NumberControl.new('Font Size', fsize , 5, 100,\n\t\t\"The size of 'The Font'.\")\n\t_font_size_pctrl.changed.connect(_on_font_size_changed)\n\t_font_size_pctrl.label.size_flags_horizontal = SIZE_SHRINK_BEGIN\n\t_ctrls.settings_bar.add_child(_font_size_pctrl)\n\tset_font_size(fsize)\n\n\n# ------------------\n# Private\n# ------------------\n\n# Call this after changes in colors and the like to get them to apply.  reloads\n# the text of the output control.\nfunc _refresh_output():\n\tvar orig_pos = _ctrls.output.scroll_vertical\n\tvar text = _ctrls.output.text\n\n\t_ctrls.output.text = text\n\t_ctrls.output.scroll_vertical = orig_pos\n\n\nfunc _create_highlighter(default_color=Color(1, 1, 1, 1)):\n\tvar to_return = CodeHighlighter.new()\n\n\tto_return.function_color = default_color\n\tto_return.number_color = default_color\n\tto_return.symbol_color = default_color\n\tto_return.member_variable_color = default_color\n\n\tfor keyword in keywords:\n\t\tto_return.add_keyword_color(keyword[0], keyword[1])\n\n\treturn to_return\n\n\nfunc _setup_colors():\n\t_ctrls.output.clear()\n\t_highlighter = _create_highlighter()\n\t_ctrls.output.queue_redraw()\n\n\n\nfunc _use_highlighting(should):\n\tif(should):\n\t\t_ctrls.output.syntax_highlighter = _highlighter\n\telse:\n\t\t_ctrls.output.syntax_highlighter = null\n\t_refresh_output()\n\n# ------------------\n# Events\n# ------------------\nfunc _on_caret_changed():\n\tvar txt = str(\"line:\",_ctrls.output.get_caret_line(), ' col:', _ctrls.output.get_caret_column())\n\t_ctrls.caret_position.text = str(txt)\n\nfunc _on_font_size_changed():\n\tset_font_size(_font_size_pctrl.value)\n\tif(_user_prefs != null):\n\t\t_user_prefs.output_font_size.value = _font_size_pctrl.value\n\t\t_user_prefs.output_font_size.save_it()\n\nfunc _on_font_name_changed():\n\tset_all_fonts(_font_name_pctrl.text)\n\tif(_user_prefs != null):\n\t\t_user_prefs.output_font_name.value = _font_name_pctrl.text\n\t\t_user_prefs.output_font_name.save_it()\n\nfunc _on_CopyButton_pressed():\n\tcopy_to_clipboard()\n\nfunc _on_UseColors_pressed():\n\t_use_highlighting(_ctrls.use_colors.button_pressed)\n\nfunc _on_ClearButton_pressed():\n\tclear()\n\nfunc _on_ShowSearch_pressed():\n\tshow_search(_ctrls.show_search.button_pressed)\n\nfunc _on_SearchTerm_focus_entered():\n\t_ctrls.search_bar.search_term.call_deferred('select_all')\n\nfunc _on_SearchNext_pressed():\n\t_sr.find_next(_ctrls.search_bar.search_term.text)\n\nfunc _on_SearchPrev_pressed():\n\t_sr.find_prev(_ctrls.search_bar.search_term.text)\n\nfunc _on_SearchTerm_text_changed(new_text):\n\tif(new_text == ''):\n\t\t_ctrls.output.deselect()\n\telse:\n\t\t_sr.find_next(new_text)\n\nfunc _on_SearchTerm_text_entered(new_text):\n\tif(Input.is_physical_key_pressed(KEY_SHIFT)):\n\t\t_sr.find_prev(new_text)\n\telse:\n\t\t_sr.find_next(new_text)\n\nfunc _on_SearchTerm_gui_input(event):\n\tif(event is InputEventKey and !event.pressed and event.keycode == KEY_ESCAPE):\n\t\tshow_search(false)\n\nfunc _on_WordWrap_pressed():\n\tif(_ctrls.word_wrap.button_pressed):\n\t\t_ctrls.output.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY\n\telse:\n\t\t_ctrls.output.wrap_mode = TextEdit.LINE_WRAPPING_NONE\n\n\t_ctrls.output.queue_redraw()\n\nfunc _on_settings_pressed():\n\t_ctrls.settings_bar.visible = $Toolbar/ShowSettings.button_pressed\n\n# ------------------\n# Public\n# ------------------\nfunc show_search(should):\n\t_ctrls.search_bar.bar.visible = should\n\tif(should):\n\t\t_ctrls.search_bar.search_term.grab_focus()\n\t\t_ctrls.search_bar.search_term.select_all()\n\t_ctrls.show_search.button_pressed = should\n\n\nfunc search(text, start_pos, highlight=true):\n\treturn _sr.find_next(text)\n\n\nfunc copy_to_clipboard():\n\tvar selected = _ctrls.output.get_selected_text()\n\tif(selected != ''):\n\t\tDisplayServer.clipboard_set(selected)\n\telse:\n\t\tDisplayServer.clipboard_set(_ctrls.output.text)\n\n\nfunc clear():\n\t_ctrls.output.text = ''\n\n\nfunc _set_font(custom_name, theme_font_name):\n\tvar font = GutUtils.gut_fonts.get_font_for_theme_font_name(theme_font_name, custom_name)\n\t_ctrls.output.add_theme_font_override(theme_font_name, font)\n\n\nfunc set_all_fonts(base_name):\n\t_font_name = GutUtils.nvl(base_name, 'Default')\n\n\t_set_font(base_name, 'font')\n\t_set_font(base_name, 'normal_font')\n\t_set_font(base_name, 'bold_font')\n\t_set_font(base_name, 'italics_font')\n\t_set_font(base_name, 'bold_italics_font')\n\n\nfunc set_font_size(new_size):\n\t_ctrls.output.set(\"theme_override_font_sizes/font_size\", new_size)\n\n\nfunc set_use_colors(value):\n\tpass\n\n\nfunc get_use_colors():\n\treturn false;\n\n\nfunc get_rich_text_edit():\n\treturn _ctrls.output\n\n\nfunc load_file(path):\n\tvar f = FileAccess.open(path, FileAccess.READ)\n\tif(f == null):\n\t\treturn\n\n\tvar t = f.get_as_text()\n\tf = null # closes file\n\t_ctrls.output.text = t\n\t_ctrls.output.scroll_vertical = _ctrls.output.get_line_count()\n\t_ctrls.output.set_deferred('scroll_vertical', _ctrls.output.get_line_count())\n\n\nfunc add_text(text):\n\tif(is_inside_tree()):\n\t\t_ctrls.output.text += text\n\n\nfunc scroll_to_line(line):\n\t_ctrls.output.scroll_vertical = line\n\t_ctrls.output.set_caret_line(line)\n"
  },
  {
    "path": "demo/addons/gut/gui/OutputText.gd.uid",
    "content": "uid://cax5phqs8acmu\n"
  },
  {
    "path": "demo/addons/gut/gui/OutputText.tscn",
    "content": "[gd_scene load_steps=5 format=3 uid=\"uid://bqmo4dj64c7yl\"]\n\n[ext_resource type=\"Script\" uid=\"uid://cax5phqs8acmu\" path=\"res://addons/gut/gui/OutputText.gd\" id=\"1\"]\n[ext_resource type=\"Texture2D\" uid=\"uid://bvo0uao7deu0q\" path=\"res://addons/gut/icon.png\" id=\"2_b4xqv\"]\n\n[sub_resource type=\"DPITexture\" id=\"DPITexture_lygvu\"]\n_source = \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"16\\\" height=\\\"16\\\"><path fill=\\\"#ff5d5d\\\" d=\\\"M2 1v8.586l1.293-1.293a1 1 0 0 1 1.414 0L7 10.587l2.293-2.293a1 1 0 0 1 1.414 0L13 10.586l1-1V6H9V1H2zm8 0v4h4zm-6 9.414-2 2V15h12v-2.586l-.293.293a1 1 0 0 1-1.414 0L10 10.414l-2.293 2.293a1 1 0 0 1-1.414 0L4 10.414z\\\"/></svg>\n\"\n\n[sub_resource type=\"CodeHighlighter\" id=\"CodeHighlighter_8ynmy\"]\nnumber_color = Color(1, 1, 1, 1)\nsymbol_color = Color(1, 1, 1, 1)\nfunction_color = Color(1, 1, 1, 1)\nmember_variable_color = Color(1, 1, 1, 1)\nkeyword_colors = {\n\"ERROR\": Color(1, 0, 0, 1),\n\"ExpectedError\": Color(0.6784314, 0.84705883, 0.9019608, 1),\n\"Failed\": Color(1, 0, 0, 1),\n\"Orphans\": Color(1, 1, 0, 1),\n\"Passed\": Color(0, 1, 0, 1),\n\"Pending\": Color(1, 1, 0, 1),\n\"Risky\": Color(1, 1, 0, 1),\n\"WARNING\": Color(1, 1, 0, 1)\n}\n\n[node name=\"OutputText\" type=\"VBoxContainer\"]\noffset_right = 862.0\noffset_bottom = 523.0\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\nscript = ExtResource(\"1\")\n\n[node name=\"Toolbar\" type=\"HBoxContainer\" parent=\".\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"ShowSearch\" type=\"Button\" parent=\"Toolbar\"]\nlayout_mode = 2\ntooltip_text = \"Search\"\ntoggle_mode = true\nicon = ExtResource(\"2_b4xqv\")\n\n[node name=\"ShowSettings\" type=\"Button\" parent=\"Toolbar\"]\nlayout_mode = 2\ntooltip_text = \"Settings\"\ntoggle_mode = true\ntext = \"...\"\n\n[node name=\"CenterContainer\" type=\"CenterContainer\" parent=\"Toolbar\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"LblPosition\" type=\"Label\" parent=\"Toolbar\"]\nlayout_mode = 2\n\n[node name=\"CopyButton\" type=\"Button\" parent=\"Toolbar\"]\nlayout_mode = 2\ntext = \" Copy \"\n\n[node name=\"ClearButton\" type=\"Button\" parent=\"Toolbar\"]\nlayout_mode = 2\ntext = \"  Clear  \"\n\n[node name=\"Settings\" type=\"HBoxContainer\" parent=\".\"]\nvisible = false\nlayout_mode = 2\n\n[node name=\"WordWrap\" type=\"Button\" parent=\"Settings\"]\nlayout_mode = 2\ntooltip_text = \"Word Wrap\"\ntoggle_mode = true\nicon = SubResource(\"DPITexture_lygvu\")\n\n[node name=\"UseColors\" type=\"Button\" parent=\"Settings\"]\nlayout_mode = 2\ntooltip_text = \"Colorized Text\"\ntoggle_mode = true\nbutton_pressed = true\nicon = SubResource(\"DPITexture_lygvu\")\n\n[node name=\"Output\" type=\"TextEdit\" parent=\".\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\ntheme_override_font_sizes/font_size = 30\ndeselect_on_focus_loss_enabled = false\nvirtual_keyboard_enabled = false\nmiddle_mouse_paste_enabled = false\nscroll_smooth = true\nsyntax_highlighter = SubResource(\"CodeHighlighter_8ynmy\")\nhighlight_all_occurrences = true\nhighlight_current_line = true\n\n[node name=\"Search\" type=\"HBoxContainer\" parent=\".\"]\nvisible = false\nlayout_mode = 2\n\n[node name=\"SearchTerm\" type=\"LineEdit\" parent=\"Search\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"SearchNext\" type=\"Button\" parent=\"Search\"]\nlayout_mode = 2\ntext = \"Next\"\n\n[node name=\"SearchPrev\" type=\"Button\" parent=\"Search\"]\nlayout_mode = 2\ntext = \"Prev\"\n\n[connection signal=\"pressed\" from=\"Toolbar/ShowSearch\" to=\".\" method=\"_on_ShowSearch_pressed\"]\n[connection signal=\"pressed\" from=\"Toolbar/ShowSettings\" to=\".\" method=\"_on_settings_pressed\"]\n[connection signal=\"pressed\" from=\"Toolbar/CopyButton\" to=\".\" method=\"_on_CopyButton_pressed\"]\n[connection signal=\"pressed\" from=\"Toolbar/ClearButton\" to=\".\" method=\"_on_ClearButton_pressed\"]\n[connection signal=\"pressed\" from=\"Settings/WordWrap\" to=\".\" method=\"_on_WordWrap_pressed\"]\n[connection signal=\"pressed\" from=\"Settings/UseColors\" to=\".\" method=\"_on_UseColors_pressed\"]\n[connection signal=\"focus_entered\" from=\"Search/SearchTerm\" to=\".\" method=\"_on_SearchTerm_focus_entered\"]\n[connection signal=\"gui_input\" from=\"Search/SearchTerm\" to=\".\" method=\"_on_SearchTerm_gui_input\"]\n[connection signal=\"text_changed\" from=\"Search/SearchTerm\" to=\".\" method=\"_on_SearchTerm_text_changed\"]\n[connection signal=\"text_submitted\" from=\"Search/SearchTerm\" to=\".\" method=\"_on_SearchTerm_text_entered\"]\n[connection signal=\"pressed\" from=\"Search/SearchNext\" to=\".\" method=\"_on_SearchNext_pressed\"]\n[connection signal=\"pressed\" from=\"Search/SearchPrev\" to=\".\" method=\"_on_SearchPrev_pressed\"]\n"
  },
  {
    "path": "demo/addons/gut/gui/ResizeHandle.gd",
    "content": "@tool\nextends ColorRect\n# #############################################################################\n# Resize Handle control.  Place onto a control.  Set the orientation, then\n# set the control that this should resize.  Then you can resize the control\n# by dragging this thing around.  It's pretty neat.\n# #############################################################################\nenum ORIENTATION {\n\tLEFT,\n\tRIGHT\n}\n\n@export var orientation := ORIENTATION.RIGHT :\n\tget: return orientation\n\tset(val):\n\t\torientation = val\n\t\tqueue_redraw()\n@export var resize_control : Control = null\n@export var vertical_resize := true\n\nvar _line_width = .5\nvar _line_color = Color(.4, .4, .4)\nvar _active_line_color = Color(.3, .3, .3)\nvar _invalid_line_color = Color(1, 0, 0)\n\nvar _line_space = 3\nvar _num_lines = 8\n\nvar _mouse_down = false\n# Called when the node enters the scene tree for the first time.\n\n\nfunc _draw():\n\tvar c = _line_color\n\tif(resize_control == null):\n\t\tc = _invalid_line_color\n\telif(_mouse_down):\n\t\tc = _active_line_color\n\n\tif(orientation == ORIENTATION.LEFT):\n\t\t_draw_resize_handle_left(c)\n\telse:\n\t\t_draw_resize_handle_right(c)\n\n\nfunc _gui_input(event):\n\tif(resize_control == null):\n\t\treturn\n\n\tif(orientation == ORIENTATION.LEFT):\n\t\t_handle_left_input(event)\n\telse:\n\t\t_handle_right_input(event)\n\n\n# Draw the lines in the corner to show where you can\n# drag to resize the dialog\nfunc _draw_resize_handle_right(draw_color):\n\tvar br = size\n\n\tfor i in range(_num_lines):\n\t\tvar start = br - Vector2(i * _line_space, 0)\n\t\tvar end = br - Vector2(0, i * _line_space)\n\t\tdraw_line(start, end, draw_color, _line_width, true)\n\n\nfunc _draw_resize_handle_left(draw_color):\n\tvar bl = Vector2(0, size.y)\n\n\tfor i in range(_num_lines):\n\t\tvar start = bl + Vector2(i * _line_space, 0)\n\t\tvar end = bl -  Vector2(0, i * _line_space)\n\t\tdraw_line(start, end, draw_color, _line_width, true)\n\n\nfunc _handle_right_input(event : InputEvent):\n\tif(event is InputEventMouseMotion):\n\t\tif(_mouse_down and\n\t\t\tevent.global_position.x > 0 and\n\t\t\tevent.global_position.y < DisplayServer.window_get_size().y):\n\n\t\t\tif(vertical_resize):\n\t\t\t\tresize_control.size.y += event.relative.y\n\t\t\tresize_control.size.x += event.relative.x\n\telif(event is InputEventMouseButton):\n\t\tif(event.button_index == MOUSE_BUTTON_LEFT):\n\t\t\t_mouse_down = event.pressed\n\t\t\tqueue_redraw()\n\n\nfunc _handle_left_input(event : InputEvent):\n\tif(event is InputEventMouseMotion):\n\t\tif(_mouse_down and\n\t\t\tevent.global_position.x > 0 and\n\t\t\tevent.global_position.y < DisplayServer.window_get_size().y):\n\n\t\t\tvar start_size = resize_control.size\n\t\t\tresize_control.size.x -= event.relative.x\n\t\t\tif(resize_control.size.x != start_size.x):\n\t\t\t\tresize_control.global_position.x += event.relative.x\n\n\t\t\tif(vertical_resize):\n\t\t\t\tresize_control.size.y += event.relative.y\n\telif(event is InputEventMouseButton):\n\t\tif(event.button_index == MOUSE_BUTTON_LEFT):\n\t\t\t_mouse_down = event.pressed\n\t\t\tqueue_redraw()\n"
  },
  {
    "path": "demo/addons/gut/gui/ResizeHandle.gd.uid",
    "content": "uid://duf6rfdqr6yoc\n"
  },
  {
    "path": "demo/addons/gut/gui/ResizeHandle.tscn",
    "content": "[gd_scene load_steps=2 format=3 uid=\"uid://bvrqqgjpyouse\"]\n\n[ext_resource type=\"Script\" uid=\"uid://duf6rfdqr6yoc\" path=\"res://addons/gut/gui/ResizeHandle.gd\" id=\"1_oi5ed\"]\n\n[node name=\"ResizeHandle\" type=\"ColorRect\"]\ncustom_minimum_size = Vector2(20, 20)\ncolor = Color(1, 1, 1, 0)\nscript = ExtResource(\"1_oi5ed\")\n"
  },
  {
    "path": "demo/addons/gut/gui/ResultsTree.gd",
    "content": "@tool\nextends Tree\n\nvar _show_orphans = true\nvar show_orphans = true :\n\tget: return _show_orphans\n\tset(val): _show_orphans = val\n\n\nvar _hide_passing = true\nvar hide_passing = true :\n\tget: return _hide_passing\n\tset(val): _hide_passing = val\n\n\nvar _icons = {\n\tred = load('res://addons/gut/images/red.png'),\n\tgreen = load('res://addons/gut/images/green.png'),\n\tyellow = load('res://addons/gut/images/yellow.png'),\n}\n\n@export var script_entry_color : Color = Color(0, 0, 0, .2) :\n\tset(val):\n\t\tif(val != null):\n\t\t\tscript_entry_color = val\n@export var column_0_color : Color = Color(1, 1, 1, 0) :\n\tset(val):\n\t\tif(val != null):\n\t\t\tcolumn_0_color = val\n@export var column_1_color : Color = Color(0, 0, 0, .2):\n\tset(val):\n\t\tif(val != null):\n\t\t\tcolumn_1_color = val\n\n\n\nvar _max_icon_width = 10\nvar _root : TreeItem\n\n\n@onready var lbl_overlay = $TextOverlay\n\n\nsignal selected(script_path, inner_class, test_name, line_number)\n\nfunc _debug_ready():\n\thide_passing = false\n\tload_json_file('user://gut_temp_directory/gut_editor.json')\n\n\nfunc _ready():\n\t_root = create_item()\n\tset_hide_root(true)\n\tcolumns = 2\n\tset_column_expand(0, true)\n\tset_column_expand_ratio(0, 5)\n\n\tset_column_expand_ratio(1, 1)\n\tset_column_expand(1, true)\n\n\titem_selected.connect(_on_tree_item_selected)\n\n\tif(get_parent() == get_tree().root):\n\t\t_debug_ready()\n\n\n# -------------------\n# Private\n# -------------------\nfunc _get_line_number_from_assert_msg(msg):\n\tvar line = -1\n\tif(msg.find('at line') > 0):\n\t\tline = msg.split(\"at line\")[-1].split(\" \")[-1].to_int()\n\treturn line\n\n\nfunc _get_path_and_inner_class_name_from_test_path(path):\n\tvar to_return = {\n\t\tpath = '',\n\t\tinner_class = ''\n\t}\n\n\tto_return.path = path\n\tif !path.ends_with('.gd'):\n\t\tvar loc = path.find('.gd')\n\t\tto_return.inner_class = path.split('.')[-1]\n\t\tto_return.path = path.substr(0, loc + 3)\n\treturn to_return\n\n\nfunc _find_script_item_with_path(path):\n\tvar items = _root.get_children()\n\tvar to_return = null\n\n\tvar idx = 0\n\twhile(idx < items.size() and to_return == null):\n\t\tvar item = items[idx]\n\t\tif(item.get_metadata(0).path == path):\n\t\t\tto_return = item\n\t\telse:\n\t\t\tidx += 1\n\n\treturn to_return\n\n\nfunc _add_script_tree_item(script_path, script_json):\n\tvar path_info = _get_path_and_inner_class_name_from_test_path(script_path)\n\tvar item_text = script_path\n\tvar parent = _root\n\n\tif(path_info.inner_class != ''):\n\t\tparent = _find_script_item_with_path(path_info.path)\n\t\titem_text = path_info.inner_class\n\t\tif(parent == null):\n\t\t\tparent = _add_script_tree_item(path_info.path, {})\n\n\tvar item = create_item(parent)\n\titem.set_text(0, item_text)\n\tvar meta = {\n\t\t\"type\":\"script\",\n\t\t\"path\":path_info.path,\n\t\t\"inner_class\":path_info.inner_class,\n\t\t\"json\":script_json,\n\t\t\"inner_passing\":0,\n\t\t\"inner_tests\":0\n\t}\n\titem.set_metadata(0, meta)\n\titem.set_custom_bg_color(0, script_entry_color)\n\titem.set_custom_bg_color(1, script_entry_color)\n\n\treturn item\n\n\nfunc _add_assert_item(text, icon, parent_item):\n\t# print('        * adding assert')\n\tvar assert_item = create_item(parent_item)\n\tassert_item.set_icon_max_width(0, _max_icon_width)\n\tassert_item.set_text(0, text)\n\tassert_item.set_metadata(0, {\"type\":\"assert\"})\n\tassert_item.set_icon(0, icon)\n\tassert_item.set_custom_bg_color(0, column_0_color)\n\tassert_item.set_custom_bg_color(1, column_1_color)\n\n\treturn assert_item\n\n\nfunc _add_test_tree_item(test_name, test_json, script_item):\n\t# print('    * adding test ', test_name)\n\tvar no_orphans_to_show = !_show_orphans or (_show_orphans and test_json.orphan_count == 0)\n\tif(_hide_passing and test_json['status'] == 'pass' and no_orphans_to_show):\n\t\treturn\n\n\tvar item = create_item(script_item)\n\tvar status = test_json['status']\n\tvar meta = {\"type\":\"test\", \"json\":test_json}\n\n\titem.set_text(0, test_name)\n\titem.set_text(1, status)\n\titem.set_text_alignment(1, HORIZONTAL_ALIGNMENT_RIGHT)\n\titem.set_custom_bg_color(1, column_1_color)\n\n\titem.set_metadata(0, meta)\n\titem.set_icon_max_width(0, _max_icon_width)\n\titem.set_custom_bg_color(0, column_0_color)\n\n\tif(status == 'pass' and no_orphans_to_show):\n\t\titem.set_icon(0, _icons.green)\n\telif(status == 'fail'):\n\t\titem.set_icon(0, _icons.red)\n\telse:\n\t\titem.set_icon(0, _icons.yellow)\n\n\tif(!_hide_passing):\n\t\tfor passing in test_json.passing:\n\t\t\t_add_assert_item('pass: ' + passing, _icons.green, item)\n\n\tfor failure in test_json.failing:\n\t\t_add_assert_item(\"fail:  \" + failure.replace(\"\\n\", ''), _icons.red, item)\n\n\tfor pending in test_json.pending:\n\t\t_add_assert_item(\"pending:  \" + pending.replace(\"\\n\", ''), _icons.yellow, item)\n\n\tvar orphan_text = 'orphans'\n\tif(test_json.orphan_count == 1):\n\t\torphan_text = 'orphan'\n\torphan_text = str(int(test_json.orphan_count), ' ', orphan_text)\n\n\tif(!no_orphans_to_show):\n\t\tvar orphan_item = _add_assert_item(orphan_text, _icons.yellow, item)\n\t\tfor o in test_json.orphans:\n\t\t\tvar orphan_entry = create_item(orphan_item)\n\t\t\torphan_entry.set_text(0, o)\n\t\t\torphan_entry.set_custom_bg_color(0, column_0_color)\n\t\t\torphan_entry.set_custom_bg_color(1, column_1_color)\n\n\treturn item\n\n\nfunc _add_script_to_tree(key, script_json):\n\tvar tests = script_json['tests']\n\tvar test_keys = tests.keys()\n\tvar s_item = _add_script_tree_item(key, script_json)\n\tvar bad_count = 0\n\n\tfor test_key in test_keys:\n\t\tvar t_item = _add_test_tree_item(test_key, tests[test_key], s_item)\n\t\tif(tests[test_key].status != 'pass'):\n\t\t\tbad_count += 1\n\t\telif(t_item != null):\n\t\t\tt_item.collapsed = true\n\n\tif(s_item.get_children().size() == 0):\n\t\tif(script_json.props.skipped):\n\t\t\t_add_assert_item(\"Skipped\", _icons.yellow, s_item)\n\t\t\ts_item.set_text(1, \"Skipped\")\n\t\telse:\n\t\t\ts_item.free()\n\telse:\n\t\tvar total_text = str('All ', test_keys.size(), ' passed')\n\t\tif(bad_count == 0):\n\t\t\ts_item.collapsed = true\n\t\telse:\n\t\t\ttotal_text = str(int(test_keys.size() - bad_count), '/', int(test_keys.size()), ' passed')\n\t\ts_item.set_text(1, total_text)\n\n\nfunc _free_childless_scripts():\n\tvar items = _root.get_children()\n\tfor item in items:\n\t\tvar next_item = item.get_next()\n\t\tif(item.get_children().size() == 0):\n\t\t\titem.free()\n\t\titem = next_item\n\n\nfunc _show_all_passed():\n\tif(_root.get_children().size() == 0):\n\t\tadd_centered_text('Everything passed!')\n\n\nfunc _load_result_tree(j):\n\tvar scripts = j['test_scripts']['scripts']\n\tvar script_keys = scripts.keys()\n\t# if we made it here, the json is valid and we did something, otherwise the\n\t# 'nothing to see here' should be visible.\n\tclear_centered_text()\n\n\tvar add_count = 0\n\tfor key in script_keys:\n\t\tadd_count += 1\n\t\t_add_script_to_tree(key, scripts[key])\n\n\t_free_childless_scripts()\n\tif(add_count == 0):\n\t\tadd_centered_text('Nothing was run')\n\telse:\n\t\t_show_all_passed()\n# -------------------\n# Events\n# -------------------\nfunc _on_tree_item_selected():\n\tvar item = get_selected()\n\tvar item_meta = item.get_metadata(0)\n\tvar item_type = null\n\n\t# Only select the left side of the tree item, cause I like that better.\n\t# you can still click the right, but only the left gets highlighted.\n\tif(item.is_selected(1)):\n\t\titem.deselect(1)\n\t\titem.select(0)\n\n\tif(item_meta == null):\n\t\treturn\n\telse:\n\t\titem_type = item_meta.type\n\n\tvar script_path = '';\n\tvar line = -1;\n\tvar test_name = ''\n\tvar inner_class = ''\n\n\tif(item_type == 'test'):\n\t\tvar s_item = item.get_parent()\n\t\tscript_path = s_item.get_metadata(0)['path']\n\t\tinner_class = s_item.get_metadata(0)['inner_class']\n\t\tline = -1\n\t\ttest_name = item.get_text(0)\n\telif(item_type == 'assert'):\n\t\tvar s_item = item.get_parent().get_parent()\n\t\tscript_path = s_item.get_metadata(0)['path']\n\t\tinner_class = s_item.get_metadata(0)['inner_class']\n\t\tline = _get_line_number_from_assert_msg(item.get_text(0))\n\t\ttest_name = item.get_parent().get_text(0)\n\telif(item_type == 'script'):\n\t\tscript_path = item.get_metadata(0)['path']\n\t\tif(item.get_parent() != _root):\n\t\t\tinner_class = item.get_text(0)\n\t\tline = -1\n\t\ttest_name = ''\n\telse:\n\t\treturn\n\n\tselected.emit(script_path, inner_class, test_name, line)\n\n\n# -------------------\n# Public\n# -------------------\nfunc load_json_file(path):\n\tvar file = FileAccess.open(path, FileAccess.READ)\n\tvar text = ''\n\tif(file != null):\n\t\ttext = file.get_as_text()\n\n\tif(text != ''):\n\t\tvar test_json_conv = JSON.new()\n\t\tvar result = test_json_conv.parse(text)\n\t\tif(result != OK):\n\t\t\tadd_centered_text(str(path, \" has invalid json in it \\n\",\n\t\t\t\t'Error ', result, \"@\", test_json_conv.get_error_line(), \"\\n\",\n\t\t\t\ttest_json_conv.get_error_message()))\n\t\t\treturn\n\n\t\tvar data = test_json_conv.get_data()\n\t\tload_json_results(data)\n\telse:\n\t\tadd_centered_text(str(path, ' was empty or does not exist.'))\n\n\nfunc load_json_results(j):\n\tclear()\n\tif(_root == null):\n\t\t_root = create_item()\n\n\t_load_result_tree(j)\n\n\n#func clear():\n\t#clear()\n\t#_root = create_item()\n\n\nfunc set_summary_min_width(width):\n\tset_column_custom_minimum_width(1, width)\n\n\nfunc add_centered_text(t):\n\tlbl_overlay.visible = true\n\tlbl_overlay.text = t\n\n\nfunc clear_centered_text():\n\tlbl_overlay.visible = false\n\tlbl_overlay.text = ''\n\n\nfunc collapse_all():\n\tset_collapsed_on_all(_root, true)\n\n\nfunc expand_all():\n\tset_collapsed_on_all(_root, false)\n\n\nfunc set_collapsed_on_all(item, value):\n\titem.set_collapsed_recursive(value)\n\tif(item == _root and value):\n\t\titem.set_collapsed(false)\n"
  },
  {
    "path": "demo/addons/gut/gui/ResultsTree.gd.uid",
    "content": "uid://dehdhn78qv5tr\n"
  },
  {
    "path": "demo/addons/gut/gui/ResultsTree.tscn",
    "content": "[gd_scene load_steps=2 format=3 uid=\"uid://dls5r5f6157nq\"]\n\n[ext_resource type=\"Script\" uid=\"uid://dehdhn78qv5tr\" path=\"res://addons/gut/gui/ResultsTree.gd\" id=\"1_b4uub\"]\n\n[node name=\"ResultsTree\" type=\"Tree\"]\noffset_right = 1082.0\noffset_bottom = 544.0\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\ncolumns = 2\nhide_root = true\nscript = ExtResource(\"1_b4uub\")\n\n[node name=\"TextOverlay\" type=\"Label\" parent=\".\"]\nvisible = false\nlayout_mode = 1\nanchors_preset = 15\nanchor_right = 1.0\nanchor_bottom = 1.0\ngrow_horizontal = 2\ngrow_vertical = 2\n\n[node name=\"ResultsTree\" type=\"VBoxContainer\" parent=\".\"]\ncustom_minimum_size = Vector2(10, 10)\nlayout_mode = 1\nanchors_preset = 15\nanchor_right = 1.0\nanchor_bottom = 1.0\noffset_right = -70.0\noffset_bottom = -104.0\ngrow_horizontal = 2\ngrow_vertical = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n"
  },
  {
    "path": "demo/addons/gut/gui/RunAtCursor.gd",
    "content": "@tool\nextends Control\n\nvar EditorCaretContextNotifier = load('res://addons/gut/editor_caret_context_notifier.gd')\n\n@onready var _ctrls = {\n\tbtn_script = $HBox/BtnRunScript,\n\tbtn_inner = $HBox/BtnRunInnerClass,\n\tbtn_method = $HBox/BtnRunMethod,\n\tlbl_none = $HBox/LblNoneSelected,\n\tarrow_1 = $HBox/Arrow1,\n\tarrow_2 = $HBox/Arrow2\n}\n\nvar _caret_notifier = null\n\nvar _last_info = {\n\tscript = null,\n\tinner_class = null,\n\tmethod = null\n}\n\nvar disabled = false :\n\tset(val):\n\t\tdisabled = val\n\t\tif(is_inside_tree()):\n\t\t\t_ctrls.btn_script.disabled = val\n\t\t\t_ctrls.btn_inner.disabled = val\n\t\t\t_ctrls.btn_method.disabled = val\nvar method_prefix = 'test_'\nvar inner_class_prefix = 'Test'\nvar menu_manager = null :\n\tset(val):\n\t\tmenu_manager = val\n\t\tmenu_manager.run_script.connect(_on_BtnRunScript_pressed)\n\t\tmenu_manager.run_at_cursor.connect(run_at_cursor)\n\t\tmenu_manager.rerun.connect(rerun)\n\t\tmenu_manager.run_inner_class.connect(_on_BtnRunInnerClass_pressed)\n\t\tmenu_manager.run_test.connect(_on_BtnRunMethod_pressed)\n\t\t_update_buttons(_last_info)\n\n\nsignal run_tests(what)\n\n\nfunc _ready():\n\t_ctrls.lbl_none.visible = true\n\t_ctrls.btn_script.visible = false\n\t_ctrls.btn_inner.visible = false\n\t_ctrls.btn_method.visible = false\n\t_ctrls.arrow_1.visible = false\n\t_ctrls.arrow_2.visible = false\n\n\t_caret_notifier = EditorCaretContextNotifier.new()\n\tadd_child(_caret_notifier)\n\t_caret_notifier.it_changed.connect(_on_caret_notifer_changed)\n\n\tdisabled = disabled\n\n\nfunc _on_caret_notifer_changed(data):\n\tif(data.is_test_script):\n\t\t_last_info = data\n\t\t_update_buttons(_last_info)\n\n\n# ----------------\n# Private\n# ----------------\n\nfunc _update_buttons(info):\n\t_ctrls.lbl_none.visible = false\n\t_ctrls.btn_script.visible = info.script != null\n\n\tif(info.script != null and info.is_test_script):\n\t\t_ctrls.btn_script.text = info.script.resource_path.get_file()\n\n\t_ctrls.btn_inner.visible = info.inner_class != null\n\t_ctrls.arrow_1.visible = info.inner_class != null\n\t_ctrls.btn_inner.text = str(info.inner_class)\n\t_ctrls.btn_inner.tooltip_text = str(\"Run all tests in Inner-Test-Class \", info.inner_class)\n\n\tvar is_test_method = info.method != null and info.method.begins_with(method_prefix)\n\t_ctrls.btn_method.visible = is_test_method\n\t_ctrls.arrow_2.visible = is_test_method\n\tif(is_test_method):\n\t\t_ctrls.btn_method.text = str(info.method)\n\t\t_ctrls.btn_method.tooltip_text = str(\"Run test \", info.method)\n\n\tif(menu_manager != null):\n\t\tmenu_manager.disable_menu(\"run_script\", info.script == null)\n\t\tmenu_manager.disable_menu(\"run_inner_class\", info.inner_class == null)\n\t\tmenu_manager.disable_menu(\"run_at_cursor\", info.script == null)\n\t\tmenu_manager.disable_menu(\"run_test\", is_test_method)\n\t\tmenu_manager.disable_menu(\"rerun\", _last_run_info == {})\n\t# The button's new size won't take effect until the next frame.\n\t# This appears to be what was causing the button to not be clickable the\n\t# first time.\n\t_update_size.call_deferred()\n\n\nfunc _update_size():\n\tcustom_minimum_size.x = _ctrls.btn_method.size.x + _ctrls.btn_method.position.x\n\nvar _last_run_info = {}\nfunc _emit_run_tests(info):\n\t_last_run_info = info.duplicate()\n\trun_tests.emit(info)\n\n# ----------------\n# Events\n# ----------------\nfunc _on_BtnRunScript_pressed():\n\tvar info = _last_info.duplicate()\n\tinfo.script = info.script.resource_path.get_file()\n\tinfo.inner_class = null\n\tinfo.method = null\n\t_emit_run_tests(info)\n\n\nfunc _on_BtnRunInnerClass_pressed():\n\tvar info = _last_info.duplicate()\n\tinfo.script = info.script.resource_path.get_file()\n\tinfo.method = null\n\t_emit_run_tests(info)\n\n\nfunc _on_BtnRunMethod_pressed():\n\tvar info = _last_info.duplicate()\n\tinfo.script = info.script.resource_path.get_file()\n\t_emit_run_tests(info)\n\n\n# ----------------\n# Public\n# ----------------\nfunc rerun():\n\tif(_last_run_info != {}):\n\t\t_emit_run_tests(_last_run_info)\n\n\nfunc run_at_cursor():\n\tif(_ctrls.btn_method.visible):\n\t\t_on_BtnRunMethod_pressed()\n\telif(_ctrls.btn_inner.visible):\n\t\t_on_BtnRunInnerClass_pressed()\n\telif(_ctrls.btn_script.visible):\n\t\t_on_BtnRunScript_pressed()\n\telse:\n\t\tprint(\"nothing selected\")\n\n\nfunc get_script_button():\n\treturn _ctrls.btn_script\n\n\nfunc get_inner_button():\n\treturn _ctrls.btn_inner\n\n\nfunc get_test_button():\n\treturn _ctrls.btn_method\n\n\nfunc set_inner_class_prefix(value):\n\t_caret_notifier.inner_class_prefix = value\n\n\nfunc apply_gut_config(gut_config):\n\t_caret_notifier.script_prefix = gut_config.options.prefix\n\t_caret_notifier.script_suffix = gut_config.options.suffix\n"
  },
  {
    "path": "demo/addons/gut/gui/RunAtCursor.gd.uid",
    "content": "uid://c4gmgdl1xwflw\n"
  },
  {
    "path": "demo/addons/gut/gui/RunAtCursor.tscn",
    "content": "[gd_scene load_steps=3 format=3 uid=\"uid://0yunjxtaa8iw\"]\n\n[ext_resource type=\"Script\" uid=\"uid://c4gmgdl1xwflw\" path=\"res://addons/gut/gui/RunAtCursor.gd\" id=\"1\"]\n[ext_resource type=\"Texture2D\" uid=\"uid://6wra5rxmfsrl\" path=\"res://addons/gut/gui/arrow.png\" id=\"3\"]\n\n[node name=\"RunAtCursor\" type=\"Control\"]\ncustom_minimum_size = Vector2(510, 0)\nlayout_mode = 3\nanchors_preset = 15\nanchor_right = 1.0\nanchor_bottom = 1.0\noffset_right = 1.0\noffset_bottom = -527.0\ngrow_horizontal = 2\ngrow_vertical = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\nscript = ExtResource(\"1\")\n\n[node name=\"HBox\" type=\"HBoxContainer\" parent=\".\"]\nlayout_mode = 0\nanchor_right = 1.0\nanchor_bottom = 1.0\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n\n[node name=\"LblNoneSelected\" type=\"Label\" parent=\"HBox\"]\nvisible = false\nlayout_mode = 2\ntext = \"<None>\"\n\n[node name=\"BtnRunScript\" type=\"Button\" parent=\"HBox\"]\nlayout_mode = 2\ntext = \"test_test.gd\"\n\n[node name=\"Arrow1\" type=\"TextureButton\" parent=\"HBox\"]\ncustom_minimum_size = Vector2(24, 0)\nlayout_mode = 2\ntexture_normal = ExtResource(\"3\")\nstretch_mode = 3\n\n[node name=\"BtnRunInnerClass\" type=\"Button\" parent=\"HBox\"]\nlayout_mode = 2\ntooltip_text = \"Run all tests in Inner-Test-Class TestAssertNe\"\ntext = \"TestAssertNe\"\n\n[node name=\"Arrow2\" type=\"TextureButton\" parent=\"HBox\"]\ncustom_minimum_size = Vector2(24, 0)\nlayout_mode = 2\ntexture_normal = ExtResource(\"3\")\nstretch_mode = 3\n\n[node name=\"BtnRunMethod\" type=\"Button\" parent=\"HBox\"]\nlayout_mode = 2\ntooltip_text = \"Run test test_fails_with_integers_equal\"\ntext = \"test_fails_with_integers_equal\"\n\n[connection signal=\"pressed\" from=\"HBox/BtnRunScript\" to=\".\" method=\"_on_BtnRunScript_pressed\"]\n[connection signal=\"pressed\" from=\"HBox/BtnRunInnerClass\" to=\".\" method=\"_on_BtnRunInnerClass_pressed\"]\n[connection signal=\"pressed\" from=\"HBox/BtnRunMethod\" to=\".\" method=\"_on_BtnRunMethod_pressed\"]\n"
  },
  {
    "path": "demo/addons/gut/gui/RunExternally.gd",
    "content": "@tool\nextends Control\n\n# I'm probably going to put this back in later and I don't want to create it\n# again.  Yeah, yeah, yeah.\n# class DotsAnimator:\n# \tvar text = ''\n# \tvar dot = '.'\n# \tvar max_dots = 3\n# \tvar dot_delay = .5\n\n# \tvar _anim_text = ''\n# \tvar _elapsed_time = 0.0\n# \tvar _cur_dots = 0\n\n# \tfunc get_animated_text():\n# \t\treturn _anim_text\n\n# \tfunc add_time(delta):\n# \t\t_elapsed_time += delta\n# \t\tif(_elapsed_time > dot_delay):\n# \t\t\t_elapsed_time = 0\n# \t\t\t_cur_dots += 1\n# \t\t\tif(_cur_dots > max_dots):\n# \t\t\t\t_cur_dots = 0\n\n# \t\t\t_anim_text = text.rpad(text.length() + _cur_dots, dot)\n\n\nvar GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')\n\n@onready var btn_kill_it = $BgControl/VBox/Kill\n@onready var bg_control = $BgControl\n\nvar _pipe_results = {}\nvar _debug_mode = false\nvar _std_thread : Thread\nvar _escape_regex : RegEx = RegEx.new()\nvar _text_buffer = ''\n\nvar bottom_panel = null :\n\tset(val):\n\t\tbottom_panel = val\n\t\tbottom_panel.resized.connect(_on_bottom_panel_resized)\nvar blocking_mode = \"Blocking\"\nvar additional_arguments = []\nvar remove_escape_characters = true\n@export var bg_color = Color.WHITE:\n\tset(val):\n\t\tbg_color = val\n\t\tif(is_inside_tree()):\n\t\t\tbg_control.get(\"theme_override_styles/panel\").bg_color = bg_color\n\n\nfunc _debug_ready():\n\t_debug_mode = true\n\tadditional_arguments = ['-gselect', 'test_awaiter.gd', '-gconfig', 'res://.gutconfig.json'] # '-gunit_test_name', 'test_can_clear_spies'\n\tblocking_mode = \"NonBlocking\"\n\trun_tests()\n\n\nfunc _ready():\n\t_escape_regex.compile(\"\\\\x1b\\\\[[0-9;]*m\")\n\tbtn_kill_it.visible = false\n\n\tif(get_parent() == get_tree().root):\n\t\t_debug_ready.call_deferred()\n\tbg_color = bg_color\n\n\nfunc _process(_delta: float) -> void:\n\tif(_pipe_results != {}):\n\t\tif(!OS.is_process_running(_pipe_results.pid)):\n\t\t\t_end_non_blocking()\n\n\n# ----------\n# Private\n# ----------\nfunc _center_me():\n\tposition = get_parent().size / 2.0 - size / 2.0\n\n\nfunc _output_text(text, should_scroll = true):\n\tif(_debug_mode):\n\t\tprint(text)\n\telse:\n\t\tif(remove_escape_characters):\n\t\t\ttext = _escape_regex.sub(text, '', true)\n\n\t\tif(bottom_panel != null):\n\t\t\tbottom_panel.add_output_text(text)\n\t\t\tif(should_scroll):\n\t\t\t\t_scroll_output_pane(-1)\n\t\telse:\n\t\t\t_text_buffer += text\n\n\nfunc _scroll_output_pane(line):\n\tif(!_debug_mode and bottom_panel != null):\n\t\tvar txt_ctrl = bottom_panel.get_text_output_control().get_rich_text_edit()\n\t\tif(line == -1):\n\t\t\tline = txt_ctrl.get_line_count()\n\t\ttxt_ctrl.scroll_vertical = line\n\n\nfunc _add_arguments_to_output():\n\tif(additional_arguments.size() != 0):\n\t\t_output_text(\n\t\t\tstr(\"Run Mode arguments: \", ' '.join(additional_arguments), \"\\n\\n\")\n\t\t)\n\n\nfunc _load_json():\n\tif(_debug_mode):\n\t\tpass # could load file and print it if we want.\n\telif(bottom_panel != null):\n\t\tbottom_panel.load_result_json()\n\n\nfunc _run_blocking(options):\n\tbtn_kill_it.visible = false\n\tvar output = []\n\tawait get_tree().create_timer(.1).timeout\n\n\tOS.execute(OS.get_executable_path(), options, output, true)\n\n\t_output_text(output[0])\n\t_add_arguments_to_output()\n\t_scroll_output_pane(-1)\n\n\t_load_json()\n\tqueue_free()\n\n\nfunc _read_non_blocking_stdio():\n\twhile(OS.is_process_running(_pipe_results.pid)):\n\t\twhile(_pipe_results.stderr.get_length() > 0):\n\t\t\t_output_text(_pipe_results.stderr.get_line() + \"\\n\")\n\n\t\twhile(_pipe_results.stdio.get_length() > 0):\n\t\t\t_output_text(_pipe_results.stdio.get_line() + \"\\n\")\n\n\t\t# without this, things start to lock up.\n\t\tawait get_tree().process_frame\n\n\nfunc _run_non_blocking(options):\n\t_pipe_results = OS.execute_with_pipe(OS.get_executable_path(), options, false)\n\t_std_thread = Thread.new()\n\t_std_thread.start(_read_non_blocking_stdio)\n\tbtn_kill_it.visible = true\n\n\nfunc _end_non_blocking():\n\t_add_arguments_to_output()\n\t_scroll_output_pane(-1)\n\n\t_load_json()\n\n\t_pipe_results = {}\n\t_std_thread.wait_to_finish()\n\t_std_thread = null\n\tqueue_free()\n\tif(_debug_mode):\n\t\tget_tree().quit()\n\n\n\n# ----------------\n# Events\n# ----------------\nfunc _on_kill_pressed() -> void:\n\tif(_pipe_results != {} and OS.is_process_running(_pipe_results.pid)):\n\t\tOS.kill(_pipe_results.pid)\n\t\tbtn_kill_it.visible = false\n\n\nfunc _on_color_rect_gui_input(event: InputEvent) -> void:\n\tif(event is InputEventMouseMotion):\n\t\tif(event.button_mask == MOUSE_BUTTON_MASK_LEFT):\n\t\t\tposition += event.relative\n\n\nfunc _on_bottom_panel_resized():\n\t_center_me()\n\n\n# ----------------\n# Public\n# ----------------\nfunc run_tests():\n\t_center_me()\n\n\tvar options = [\"-s\", \"res://addons/gut/gut_cmdln.gd\", \"-graie\", \"-gdisable_colors\",\n\t\t\"-gconfig\", GutEditorGlobals.editor_run_gut_config_path]\n\toptions.append_array(additional_arguments)\n\n\tif(blocking_mode == 'Blocking'):\n\t\t_run_blocking(options)\n\telse:\n\t\t_run_non_blocking(options)\n\n\nfunc get_godot_help():\n\t_text_buffer = ''\n\tvar options = [\"--help\", \"--headless\"]\n\tawait _run_blocking(options)\n\treturn _text_buffer\n\n\nfunc get_gut_help():\n\t_text_buffer = ''\n\tvar options = [\"-s\", \"res://addons/gut/gut_cmdln.gd\", \"-gh\", \"--headless\"]\n\tawait _run_blocking(options)\n\treturn _text_buffer\n"
  },
  {
    "path": "demo/addons/gut/gui/RunExternally.gd.uid",
    "content": "uid://bi8pg352un4om\n"
  },
  {
    "path": "demo/addons/gut/gui/RunExternally.tscn",
    "content": "[gd_scene load_steps=3 format=3 uid=\"uid://cftcb0e6g7tu1\"]\n\n[ext_resource type=\"Script\" uid=\"uid://bi8pg352un4om\" path=\"res://addons/gut/gui/RunExternally.gd\" id=\"1_lrqqi\"]\n\n[sub_resource type=\"StyleBoxFlat\" id=\"StyleBoxFlat_haowt\"]\nbg_color = Color(0.025935074, 0.17817128, 0.30283752, 1)\ncorner_radius_top_left = 20\ncorner_radius_top_right = 20\ncorner_radius_bottom_right = 20\ncorner_radius_bottom_left = 20\nshadow_size = 5\nshadow_offset = Vector2(7, 7)\n\n[node name=\"DoShellOut\" type=\"Control\"]\nlayout_mode = 3\nanchors_preset = 0\noffset_right = 774.0\noffset_bottom = 260.0\nscript = ExtResource(\"1_lrqqi\")\nbg_color = Color(0.025935074, 0.17817128, 0.30283752, 1)\n\n[node name=\"BgControl\" type=\"Panel\" parent=\".\"]\nlayout_mode = 1\nanchors_preset = 15\nanchor_right = 1.0\nanchor_bottom = 1.0\ngrow_horizontal = 2\ngrow_vertical = 2\ntheme_override_styles/panel = SubResource(\"StyleBoxFlat_haowt\")\n\n[node name=\"VBox\" type=\"VBoxContainer\" parent=\"BgControl\"]\nlayout_mode = 1\nanchors_preset = 15\nanchor_right = 1.0\nanchor_bottom = 1.0\ngrow_horizontal = 2\ngrow_vertical = 2\n\n[node name=\"Spacer\" type=\"CenterContainer\" parent=\"BgControl/VBox\"]\nlayout_mode = 2\nsize_flags_vertical = 3\n\n[node name=\"Title\" type=\"Label\" parent=\"BgControl/VBox\"]\nlayout_mode = 2\ntext = \"Running Tests\"\nhorizontal_alignment = 1\n\n[node name=\"Spacer2\" type=\"CenterContainer\" parent=\"BgControl/VBox\"]\nvisible = false\nlayout_mode = 2\nsize_flags_vertical = 3\n\n[node name=\"Kill\" type=\"Button\" parent=\"BgControl/VBox\"]\nvisible = false\ncustom_minimum_size = Vector2(200, 50)\nlayout_mode = 2\nsize_flags_horizontal = 4\ntext = \"Stop\"\n\n[node name=\"Spacer3\" type=\"CenterContainer\" parent=\"BgControl/VBox\"]\nlayout_mode = 2\nsize_flags_vertical = 3\n\n[connection signal=\"gui_input\" from=\"BgControl\" to=\".\" method=\"_on_color_rect_gui_input\"]\n[connection signal=\"pressed\" from=\"BgControl/VBox/Kill\" to=\".\" method=\"_on_kill_pressed\"]\n"
  },
  {
    "path": "demo/addons/gut/gui/RunResults.gd",
    "content": "@tool\nextends Control\n\nvar GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')\n\nvar _interface = null\nvar _output_control = null\n\n@onready var _ctrls = {\n\ttree = $VBox/Output/Scroll/Tree,\n\ttoolbar = {\n\t\ttoolbar = $VBox/Toolbar,\n\t\tcollapse = $VBox/Toolbar/Collapse,\n\t\tcollapse_all = $VBox/Toolbar/CollapseAll,\n\t\texpand = $VBox/Toolbar/Expand,\n\t\texpand_all = $VBox/Toolbar/ExpandAll,\n\t\thide_passing = $VBox/Toolbar/HidePassing,\n\t\tshow_script = $VBox/Toolbar/ShowScript,\n\t\tscroll_output = $VBox/Toolbar/ScrollOutput\n\t}\n}\n\nfunc _ready():\n\tif(get_parent() is SubViewport):\n\t\treturn\n\n\tvar f = null\n\tif ($FontSampler.get_label_settings() == null) :\n\t\tf = get_theme_default_font()\n\telse :\n\t\tf = $FontSampler.get_label_settings().font\n\tvar s_size = f.get_string_size(\"000 of 000 passed\")\n\t_ctrls.tree.set_summary_min_width(s_size.x)\n\n\t_set_toolbutton_icon(_ctrls.toolbar.collapse, 'CollapseTree', 'c')\n\t_set_toolbutton_icon(_ctrls.toolbar.collapse_all, 'CollapseTree', 'c')\n\t_set_toolbutton_icon(_ctrls.toolbar.expand, 'ExpandTree', 'e')\n\t_set_toolbutton_icon(_ctrls.toolbar.expand_all, 'ExpandTree', 'e')\n\t_set_toolbutton_icon(_ctrls.toolbar.show_script, 'Script', 'ss')\n\t_set_toolbutton_icon(_ctrls.toolbar.scroll_output, 'Font', 'so')\n\n\t_ctrls.tree.hide_passing = true\n\t_ctrls.toolbar.hide_passing.button_pressed = false\n\t_ctrls.tree.show_orphans = true\n\t_ctrls.tree.selected.connect(_on_item_selected)\n\n\tif(get_parent() == get_tree().root):\n\t\t_test_running_setup()\n\n\tcall_deferred('_update_min_width')\n\n\nfunc _test_running_setup():\n\t_ctrls.tree.hide_passing = true\n\t_ctrls.tree.show_orphans = true\n\n\t_ctrls.toolbar.hide_passing.text = '[hp]'\n\t_ctrls.tree.load_json_file(GutEditorGlobals.editor_run_json_results_path)\n\n\nfunc _set_toolbutton_icon(btn, icon_name, text):\n\tif(Engine.is_editor_hint()):\n\t\tbtn.icon = get_theme_icon(icon_name, 'EditorIcons')\n\telse:\n\t\tbtn.text = str('[', text, ']')\n\n\nfunc _update_min_width():\n\tcustom_minimum_size.x = _ctrls.toolbar.toolbar.size.x\n\n\nfunc _open_script_in_editor(path, line_number):\n\tif(_interface == null):\n\t\tprint('Too soon, wait a bit and try again.')\n\t\treturn\n\n\tvar r = load(path)\n\tif(line_number != null and line_number != -1):\n\t\t_interface.edit_script(r, line_number)\n\telse:\n\t\t_interface.edit_script(r)\n\n\tif(_ctrls.toolbar.show_script.pressed):\n\t\t_interface.set_main_screen_editor('Script')\n\n\n# starts at beginning of text edit and searches for each search term, moving\n# through the text as it goes; ensuring that, when done, it found the first\n# occurance of the last srting that happend after the first occurance of\n# each string before it.  (Generic way of searching for a method name in an\n# inner class that may have be a duplicate of a method name in a different\n# inner class)\nfunc _get_line_number_for_seq_search(search_strings, te):\n\tif(te == null):\n\t\tprint(\"No Text editor to get line number for\")\n\t\treturn 0;\n\n\tvar result = null\n\tvar line = Vector2i(0, 0)\n\tvar s_flags = 0\n\n\tvar i = 0\n\tvar string_found = true\n\twhile(i < search_strings.size() and string_found):\n\t\tresult = te.search(search_strings[i], s_flags, line.y, line.x)\n\t\tif(result.x != -1):\n\t\t\tline = result\n\t\telse:\n\t\t\tstring_found = false\n\t\ti += 1\n\n\treturn line.y\n\n\nfunc _goto_code(path, line, method_name='', inner_class =''):\n\tif(_interface == null):\n\t\tprint('going to ', [path, line, method_name, inner_class])\n\t\treturn\n\n\t_open_script_in_editor(path, line)\n\tif(line == -1):\n\t\tvar search_strings = []\n\t\tif(inner_class != ''):\n\t\t\tsearch_strings.append(inner_class)\n\n\t\tif(method_name != ''):\n\t\t\tsearch_strings.append(method_name)\n\n\t\tawait get_tree().process_frame\n\t\tline = _get_line_number_for_seq_search(search_strings,\n\t\t\t_interface.get_script_editor().get_current_editor().get_base_editor())\n\t\tif(line != null and line != -1):\n\t\t\t_interface.get_script_editor().goto_line(line)\n\n\nfunc _goto_output(path, method_name, inner_class):\n\tif(_output_control == null):\n\t\treturn\n\n\tvar search_strings = [path]\n\n\tif(inner_class != ''):\n\t\tsearch_strings.append(inner_class)\n\n\tif(method_name != ''):\n\t\tsearch_strings.append(method_name)\n\n\tvar line = _get_line_number_for_seq_search(search_strings, _output_control.get_rich_text_edit())\n\tif(line != null and line != -1):\n\t\t_output_control.scroll_to_line(line)\n\n\n\n\n# --------------\n# Events\n# --------------\nfunc _on_Collapse_pressed():\n\tcollapse_selected()\n\n\nfunc _on_Expand_pressed():\n\texpand_selected()\n\n\nfunc _on_CollapseAll_pressed():\n\tcollapse_all()\n\n\nfunc _on_ExpandAll_pressed():\n\texpand_all()\n\n\nfunc _on_Hide_Passing_pressed():\n\t_ctrls.tree.hide_passing = !_ctrls.toolbar.hide_passing.button_pressed\n\t_ctrls.tree.load_json_file(GutEditorGlobals.editor_run_json_results_path)\n\n\nfunc _on_item_selected(script_path, inner_class, test_name, line):\n\tif(_ctrls.toolbar.show_script.button_pressed):\n\t\t_goto_code(script_path, line, test_name, inner_class)\n\tif(_ctrls.toolbar.scroll_output.button_pressed):\n\t\t_goto_output(script_path, test_name, inner_class)\n\n\n\n\n# --------------\n# Public\n# --------------\nfunc add_centered_text(t):\n\t_ctrls.tree.add_centered_text(t)\n\n\nfunc clear_centered_text():\n\t_ctrls.tree.clear_centered_text()\n\n\nfunc clear():\n\t_ctrls.tree.clear()\n\tclear_centered_text()\n\n\nfunc set_interface(which):\n\t_interface = which\n\n\nfunc collapse_all():\n\t_ctrls.tree.collapse_all()\n\n\nfunc expand_all():\n\t_ctrls.tree.expand_all()\n\n\nfunc collapse_selected():\n\tvar item = _ctrls.tree.get_selected()\n\tif(item != null):\n\t\t_ctrls.tree.set_collapsed_on_all(item, true)\n\n\nfunc expand_selected():\n\tvar item = _ctrls.tree.get_selected()\n\tif(item != null):\n\t\t_ctrls.tree.set_collapsed_on_all(item, false)\n\n\nfunc set_show_orphans(should):\n\t_ctrls.tree.show_orphans = should\n\n\nfunc set_font(font_name, size):\n\tpass\n#\tvar dyn_font = FontFile.new()\n#\tvar font_data = FontFile.new()\n#\tfont_data.font_path = 'res://addons/gut/fonts/' + font_name + '-Regular.ttf'\n#\tfont_data.antialiased = true\n#\tdyn_font.font_data = font_data\n#\n#\t_font = dyn_font\n#\t_font.size = size\n#\t_font_size = size\n\n\nfunc set_output_control(value):\n\t_output_control = value\n\n\nfunc load_json_results(j):\n\t_ctrls.tree.load_json_results(j)\n"
  },
  {
    "path": "demo/addons/gut/gui/RunResults.gd.uid",
    "content": "uid://chnko3073tkcv\n"
  },
  {
    "path": "demo/addons/gut/gui/RunResults.tscn",
    "content": "[gd_scene load_steps=4 format=3 uid=\"uid://4gyyn12um08h\"]\n\n[ext_resource type=\"Script\" uid=\"uid://chnko3073tkcv\" path=\"res://addons/gut/gui/RunResults.gd\" id=\"1\"]\n[ext_resource type=\"Texture2D\" uid=\"uid://bvo0uao7deu0q\" path=\"res://addons/gut/icon.png\" id=\"2_1k8e0\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://dls5r5f6157nq\" path=\"res://addons/gut/gui/ResultsTree.tscn\" id=\"2_o808v\"]\n\n[node name=\"RunResults\" type=\"Control\"]\ncustom_minimum_size = Vector2(345, 0)\nlayout_mode = 3\nanchors_preset = 0\noffset_right = 709.0\noffset_bottom = 321.0\nscript = ExtResource(\"1\")\n\n[node name=\"VBox\" type=\"VBoxContainer\" parent=\".\"]\nlayout_mode = 0\nanchor_right = 1.0\nanchor_bottom = 1.0\n\n[node name=\"Toolbar\" type=\"HBoxContainer\" parent=\"VBox\"]\nlayout_mode = 2\nsize_flags_horizontal = 0\n\n[node name=\"Expand\" type=\"Button\" parent=\"VBox/Toolbar\"]\nvisible = false\nlayout_mode = 2\nicon = ExtResource(\"2_1k8e0\")\n\n[node name=\"Collapse\" type=\"Button\" parent=\"VBox/Toolbar\"]\nvisible = false\nlayout_mode = 2\nicon = ExtResource(\"2_1k8e0\")\n\n[node name=\"Sep\" type=\"ColorRect\" parent=\"VBox/Toolbar\"]\nvisible = false\ncustom_minimum_size = Vector2(2, 0)\nlayout_mode = 2\n\n[node name=\"LblAll\" type=\"Label\" parent=\"VBox/Toolbar\"]\nvisible = false\nlayout_mode = 2\ntext = \"All:\"\n\n[node name=\"ExpandAll\" type=\"Button\" parent=\"VBox/Toolbar\"]\nlayout_mode = 2\nicon = ExtResource(\"2_1k8e0\")\n\n[node name=\"CollapseAll\" type=\"Button\" parent=\"VBox/Toolbar\"]\nlayout_mode = 2\nicon = ExtResource(\"2_1k8e0\")\n\n[node name=\"Sep2\" type=\"ColorRect\" parent=\"VBox/Toolbar\"]\ncustom_minimum_size = Vector2(2, 0)\nlayout_mode = 2\n\n[node name=\"HidePassing\" type=\"CheckBox\" parent=\"VBox/Toolbar\"]\nlayout_mode = 2\nsize_flags_horizontal = 4\ntext = \"Passing\"\n\n[node name=\"Sep3\" type=\"ColorRect\" parent=\"VBox/Toolbar\"]\ncustom_minimum_size = Vector2(2, 0)\nlayout_mode = 2\n\n[node name=\"LblSync\" type=\"Label\" parent=\"VBox/Toolbar\"]\nlayout_mode = 2\ntext = \"Sync:\"\n\n[node name=\"ShowScript\" type=\"Button\" parent=\"VBox/Toolbar\"]\nlayout_mode = 2\ntoggle_mode = true\nbutton_pressed = true\nicon = ExtResource(\"2_1k8e0\")\n\n[node name=\"ScrollOutput\" type=\"Button\" parent=\"VBox/Toolbar\"]\nlayout_mode = 2\ntoggle_mode = true\nbutton_pressed = true\nicon = ExtResource(\"2_1k8e0\")\n\n[node name=\"Output\" type=\"Panel\" parent=\"VBox\"]\nself_modulate = Color(1, 1, 1, 0.541176)\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n\n[node name=\"Scroll\" type=\"ScrollContainer\" parent=\"VBox/Output\"]\nlayout_mode = 1\nanchors_preset = 15\nanchor_right = 1.0\nanchor_bottom = 1.0\ngrow_horizontal = 2\ngrow_vertical = 2\n\n[node name=\"Tree\" parent=\"VBox/Output/Scroll\" instance=ExtResource(\"2_o808v\")]\nlayout_mode = 2\n\n[node name=\"FontSampler\" type=\"Label\" parent=\".\"]\nvisible = false\nlayout_mode = 0\noffset_right = 40.0\noffset_bottom = 14.0\ntext = \"000 of 000 passed\"\n\n[connection signal=\"pressed\" from=\"VBox/Toolbar/Expand\" to=\".\" method=\"_on_Expand_pressed\"]\n[connection signal=\"pressed\" from=\"VBox/Toolbar/Collapse\" to=\".\" method=\"_on_Collapse_pressed\"]\n[connection signal=\"pressed\" from=\"VBox/Toolbar/ExpandAll\" to=\".\" method=\"_on_ExpandAll_pressed\"]\n[connection signal=\"pressed\" from=\"VBox/Toolbar/CollapseAll\" to=\".\" method=\"_on_CollapseAll_pressed\"]\n[connection signal=\"pressed\" from=\"VBox/Toolbar/HidePassing\" to=\".\" method=\"_on_Hide_Passing_pressed\"]\n"
  },
  {
    "path": "demo/addons/gut/gui/Settings.tscn",
    "content": "[gd_scene format=3 uid=\"uid://cvvvtsah38l0e\"]\n\n[node name=\"Settings\" type=\"VBoxContainer\"]\noffset_right = 388.0\noffset_bottom = 586.0\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n"
  },
  {
    "path": "demo/addons/gut/gui/ShellOutOptions.gd",
    "content": "@tool\nextends ConfirmationDialog\n\nconst RUN_MODE_EDITOR = 'Editor'\nconst RUN_MODE_BLOCKING = 'Blocking'\nconst RUN_MODE_NON_BLOCKING = 'NonBlocking'\n\nvar GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')\n\n@onready var _bad_arg_dialog = $AcceptDialog\n@onready var _main_container = $ScrollContainer/VBoxContainer\n\nvar _blurb_style_box = StyleBoxEmpty.new()\nvar _opt_maker_setup = false\nvar _arg_vbox : VBoxContainer = null\nvar _my_ok_button : Button = null\n\n# Run mode button stuff\nvar _run_mode_theme = load('res://addons/gut/gui/EditorRadioButton.tres')\nvar _button_group = ButtonGroup.new()\nvar _btn_in_editor : Button = null\nvar _btn_blocking : Button = null\nvar _btn_non_blocking : Button = null\nvar _txt_additional_arguments = null\nvar _btn_godot_help = null\nvar _btn_gut_help = null\n\n\nvar opt_maker = null\nvar default_path = GutEditorGlobals.run_externally_options_path\n# I like this.  It holds values loaded/saved which makes for an easy\n# reset mechanism.  Hit OK; values get written to this object (not the file\n# system).  Hit Cancel; values are reloaded from this object.  Call the\n# save/load methods to interact with the file system.\n#\n# Downside:  If the keys/sections in the config file change, this ends up\n#            preserving old data.  So you gotta find a way to clean it out\n#            somehow.\n# Downside solved:  Clear the config file at the start of the save method.\nvar _config_file = ConfigFile.new()\n\nvar _run_mode = RUN_MODE_EDITOR\nvar run_mode = _run_mode:\n\tset(val):\n\t\t_run_mode = val\n\t\tif(is_inside_tree()):\n\t\t\t_btn_in_editor.button_pressed = _run_mode == RUN_MODE_EDITOR\n\t\t\tif(_btn_in_editor.button_pressed):\n\t\t\t\t_btn_in_editor.pressed.emit()\n\t\t\t_btn_blocking.button_pressed = _run_mode == RUN_MODE_BLOCKING\n\t\t\tif(_btn_blocking.button_pressed):\n\t\t\t\t_btn_blocking.pressed.emit()\n\t\t\t_btn_non_blocking.button_pressed = _run_mode == RUN_MODE_NON_BLOCKING\n\t\t\tif(_btn_non_blocking.button_pressed):\n\t\t\t\t_btn_non_blocking.pressed.emit()\n\tget():\n\t\treturn _run_mode\n\n\nvar additional_arguments = '' :\n\tget():\n\t\tif(_opt_maker_setup):\n\t\t\treturn opt_maker.controls.additional_arguments.value\n\t\telse:\n\t\t\treturn additional_arguments\n\n\nfunc _debug_ready():\n\tpopup_centered()\n\tdefault_path = GutEditorGlobals.temp_directory.path_join('test_external_run_options.cfg')\n\texclusive = false\n\n\tvar save_btn = Button.new()\n\tsave_btn.text = 'save'\n\tsave_btn.pressed.connect(func():\n\t\tsave_to_file()\n\t\tprint(_config_file.encode_to_text()))\n\tsave_btn.position = Vector2(100, 20)\n\tsave_btn.size = Vector2(100, 100)\n\tget_tree().root.add_child(save_btn)\n\n\tvar load_btn = Button.new()\n\tload_btn.text = 'load'\n\tload_btn.pressed.connect(func():\n\t\tload_from_file()\n\t\tprint(_config_file.encode_to_text()))\n\tload_btn.position = Vector2(100, 130)\n\tload_btn.size = Vector2(100, 100)\n\tget_tree().root.add_child(load_btn)\n\n\tvar show_btn = Button.new()\n\tshow_btn.text = 'Show'\n\tshow_btn.pressed.connect(popup_centered)\n\tshow_btn.position = Vector2(100, 250)\n\tshow_btn.size = Vector2(100, 100)\n\tget_tree().root.add_child(show_btn)\n\n\nfunc _ready():\n\topt_maker = GutUtils.OptionMaker.new(_main_container)\n\t_add_controls()\n\n\tif(get_parent() == get_tree().root):\n\t\t_debug_ready.call_deferred()\n\n\t_my_ok_button = Button.new()\n\t_my_ok_button.text = 'OK'\n\t_my_ok_button.pressed.connect(_validate_and_confirm)\n\tget_ok_button().add_sibling(_my_ok_button)\n\tget_ok_button().modulate.a = 0.0\n\tget_ok_button().text = ''\n\tget_ok_button().disabled = true\n\n\tcanceled.connect(reset)\n\t_button_group.pressed.connect(_on_mode_button_pressed)\n\trun_mode = run_mode\n\n\nfunc _validate_and_confirm():\n\tif(validate_arguments()):\n\t\t_save_to_config_file(_config_file)\n\t\tconfirmed.emit()\n\t\thide()\n\telse:\n\t\tvar dlg_text = str(\"Invalid arguments.  The following cannot be used:\\n\",\n\t\t\t' '.join(_invalid_args))\n\n\t\tif(run_mode == RUN_MODE_BLOCKING):\n\t\t\tdlg_text += str(\"\\nThese cannot be used with blocking mode:\\n\",\n\t\t\t\t' '.join(_invalid_blocking_args))\n\n\t\t_bad_arg_dialog.dialog_text = dlg_text\n\t\t_bad_arg_dialog.popup_centered()\n\n\nfunc _on_mode_button_pressed(which):\n\tif(which == _btn_in_editor):\n\t\t_arg_vbox.modulate.a = .3\n\telse:\n\t\t_arg_vbox.modulate.a = 1.0\n\n\t_txt_additional_arguments.value_ctrl.editable = which != _btn_in_editor\n\tif(which == _btn_in_editor):\n\t\t_run_mode = RUN_MODE_EDITOR\n\telif(which == _btn_blocking):\n\t\t_run_mode = RUN_MODE_BLOCKING\n\telif(which == _btn_non_blocking):\n\t\t_run_mode = RUN_MODE_NON_BLOCKING\n\n\nfunc _add_run_mode_button(text, desc_label, description):\n\tvar btn = Button.new()\n\tbtn.size_flags_horizontal = Control.SIZE_EXPAND_FILL\n\tbtn.toggle_mode = true\n\tbtn.text = text\n\tbtn.button_group = _button_group\n\tbtn.theme = _run_mode_theme\n\tbtn.pressed.connect(func():  desc_label.text = str('[b]', text, \"[/b]\\n\", description))\n\n\treturn btn\n\n\nfunc _add_blurb(text):\n\tvar ctrl = opt_maker.add_blurb(text)\n\tctrl.set(\"theme_override_styles/normal\", _blurb_style_box)\n\treturn ctrl\n\n\nfunc _add_title(text):\n\tvar ctrl = opt_maker.add_title(text)\n\tctrl.get_child(0).horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER\n\treturn ctrl\n\n\nfunc _add_controls():\n\t_add_title(\"Run Modes\")\n\t_add_blurb(\n\t\t\"Choose how GUT will launch tests.  Normally you just run them through the editor, but now \" +\n\t\t\"you can run them externally.  This is an experimental feature.  It has been tested on Mac \" +\n\t\t\"and Windows.  Your results may vary.  Feedback welcome at [url]https://github.com/bitwes/Gut/issues[/url].\\n \")\n\n\tvar button_desc_box = HBoxContainer.new()\n\tvar button_box = VBoxContainer.new()\n\tvar button_desc = RichTextLabel.new()\n\tbutton_desc.fit_content = true\n\tbutton_desc.bbcode_enabled = true\n\tbutton_desc.size_flags_horizontal = Control.SIZE_EXPAND_FILL\n\tbutton_desc.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART\n\t_main_container.add_child(button_desc_box)\n\tbutton_desc_box.add_child(button_box)\n\tbutton_desc_box.add_child(button_desc)\n\n\t_btn_in_editor = _add_run_mode_button(\"In Editor (default)\", button_desc,\n\t\t\"This is the default.  Runs through the editor.  When an error occurs \" +\n\t\t\"the debugger is invoked.  [b]print[/b] output \" +\n\t\t\"appears in the Output panel and errors show up in the Debugger panel.\")\n\tbutton_box.add_child(_btn_in_editor)\n\t_btn_blocking = _add_run_mode_button(\"Externally - Blocking\", button_desc,\n\t\t\"Debugger is not enabled, and cannot be enabled.  All output (print, errors, warnings, etc) \" +\n\t\t\"appears in the GUT panel, and [b]not[/b] the Output or Debugger panels.  \\n\" +\n\t\t\"The Editor cannot be used while tests are running.  If you are trying to test for errors, this \" +\n\t\t\"mode provides the best output.\")\n\tbutton_box.add_child(_btn_blocking)\n\t_btn_non_blocking = _add_run_mode_button(\"Externally - NonBlocking\", button_desc,\n\t\t\"Debugger is not enabled, and cannot be enabled.  All output (print, errors, warnings, etc) \" +\n\t\t\"appears in the GUT panel, and [b]not[/b] the Output or Debugger panels.  \\n\" +\n\t\t\"Test output is streamed to the GUT panel.  The editor is not blocked, but can be less \" +\n\t\t\"responsive when there is a lot of output.  This is the only mode that supports the --headless argument.\" )\n\tbutton_box.add_child(_btn_non_blocking)\n\n\t_add_title(\"Command Line Arguments\")\n\t_arg_vbox = VBoxContainer.new()\n\t_main_container.add_child(_arg_vbox)\n\topt_maker.base_container = _arg_vbox\n\t_txt_additional_arguments = opt_maker.add_value(\"additional_arguments\", additional_arguments, '', '')\n\t_txt_additional_arguments.value_ctrl.placeholder_text = \"Put your arguments here.  Ex:  --verbose -glog 0\"\n\t_txt_additional_arguments.value_ctrl.select_all_on_focus = false\n\t_add_blurb(\n\t\t\"Supply any command line options for GUT and/or Godot when running externally.  You cannot use \" +\n\t\t\"spaces in values.  See the Godot and GUT documentation for valid arguments.  GUT arguments \" + \n\t\t\"specified here take precedence over your config.\")\n\t_add_blurb(\"[b]Be Careful[/b]  There are plenty of argument combinations that may make this \" +\n\t\t\"act wrong/odd/bad/horrible.  Some arguments you might [i]want[/i] \" +\n\t\t\"to use but [b]shouldn't[/b] are checked for, but not that many.  Choose your arguments carefully (generally good advice).\")\n\n\topt_maker.base_container = _main_container\n\t_add_title(\"Display CLI Help\")\n\t_add_blurb(\"You can use these buttons to get a list of valid GUT and Godot options.  They print the CLI help text for each to the [b]Output Panel[/b].\")\n\t_btn_godot_help = Button.new()\n\t_btn_godot_help.text = \"Print Godot CLI Help\"\n\t_main_container.add_child(_btn_godot_help)\n\t_btn_godot_help.pressed.connect(func():\n\t\tawait _show_help(\"get_godot_help\"))\n\n\t_btn_gut_help = Button.new()\n\t_btn_gut_help.text = \"Print GUT CLI Help\"\n\t_main_container.add_child(_btn_gut_help)\n\t_btn_gut_help.pressed.connect(func():\n\t\tawait _show_help(\"get_gut_help\"))\n\n\t_opt_maker_setup = true\n\n\nfunc _show_help(help_method_name):\n\t_btn_godot_help.disabled = true\n\t_btn_gut_help.disabled = true\n\tvar re = GutUtils.RunExternallyScene.instantiate()\n\tadd_child(re)\n\tre.visible = false\n\tvar text = await re.call(help_method_name)\n\tprint(text)\n\tre.queue_free()\n\t_btn_godot_help.disabled = false\n\t_btn_gut_help.disabled = false\n\tif(GutEditorGlobals.gut_plugin != null):\n\t\tGutEditorGlobals.gut_plugin.show_output_panel()\n\n\nfunc _save_to_config_file(f : ConfigFile):\n\tf.clear()\n\tf.set_value('main', 'run_mode', run_mode)\n\tf.set_value('main', 'additional_arguments', opt_maker.controls.additional_arguments.value)\n\n\nfunc save_to_file(path = default_path):\n\t_save_to_config_file(_config_file)\n\t_config_file.save(path)\n\n\nfunc _load_from_config_file(f):\n\trun_mode = f.get_value('main', 'run_mode', RUN_MODE_EDITOR)\n\topt_maker.controls.additional_arguments.value = \\\n\t\tf.get_value('main', 'additional_arguments', '')\n\n\nfunc load_from_file(path = default_path):\n\t_config_file.load(path)\n\t_load_from_config_file(_config_file)\n\n\nfunc reset():\n\t_load_from_config_file(_config_file)\n\n\nfunc get_additional_arguments_array():\n\treturn additional_arguments.split(\" \", false)\n\n\nfunc should_run_externally():\n\treturn run_mode != RUN_MODE_EDITOR\n\n\nvar _invalid_args = [\n\t'-d', '--debug',\n\t'-s', '--script',\n\t'-e', '--editor'\n]\nvar _invalid_blocking_args = [\n\t'--headless'\n]\nfunc validate_arguments():\n\tvar arg_array = get_additional_arguments_array()\n\tvar i = 0\n\tvar invalid_found = false\n\twhile i < _invalid_args.size() and !invalid_found:\n\t\tif(arg_array.has(_invalid_args[i])):\n\t\t\tinvalid_found = true\n\t\ti += 1\n\n\tif(run_mode == RUN_MODE_BLOCKING):\n\t\ti = 0\n\t\twhile i < _invalid_blocking_args.size() and !invalid_found:\n\t\t\tif(arg_array.has(_invalid_blocking_args[i])):\n\t\t\t\tinvalid_found = true\n\t\t\ti += 1\n\n\treturn !invalid_found\n\n\nfunc get_godot_help():\n\treturn ''\n"
  },
  {
    "path": "demo/addons/gut/gui/ShellOutOptions.gd.uid",
    "content": "uid://c64u22kybimgi\n"
  },
  {
    "path": "demo/addons/gut/gui/ShellOutOptions.tscn",
    "content": "[gd_scene load_steps=2 format=3 uid=\"uid://ckv5eh8xyrwbk\"]\n\n[ext_resource type=\"Script\" uid=\"uid://c64u22kybimgi\" path=\"res://addons/gut/gui/ShellOutOptions.gd\" id=\"1_ht2pf\"]\n\n[node name=\"ShellOutOptions\" type=\"ConfirmationDialog\"]\noversampling_override = 1.0\ntitle = \"GUT Run Mode (Experimental)\"\nposition = Vector2i(0, 36)\nsize = Vector2i(516, 557)\nvisible = true\nscript = ExtResource(\"1_ht2pf\")\n\n[node name=\"ScrollContainer\" type=\"ScrollContainer\" parent=\".\"]\ncustom_minimum_size = Vector2(500, 500)\nanchors_preset = 15\nanchor_right = 1.0\nanchor_bottom = 1.0\noffset_left = 8.0\noffset_top = 8.0\noffset_right = -8.0\noffset_bottom = -49.0\ngrow_horizontal = 2\ngrow_vertical = 2\n\n[node name=\"VBoxContainer\" type=\"VBoxContainer\" parent=\"ScrollContainer\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n\n[node name=\"AcceptDialog\" type=\"AcceptDialog\" parent=\".\"]\noversampling_override = 1.0\nsize = Vector2i(399, 106)\ndialog_text = \"Invalid arguments.  The following cannot be used:\n-d --debug -s --script\"\n"
  },
  {
    "path": "demo/addons/gut/gui/ShortcutButton.gd",
    "content": "@tool\nextends Control\n\n\n@onready var _ctrls = {\n\tshortcut_label = $Layout/lblShortcut,\n\tset_button = $Layout/SetButton,\n\tsave_button = $Layout/SaveButton,\n\tcancel_button = $Layout/CancelButton,\n\tclear_button = $Layout/ClearButton\n}\n\nsignal changed\nsignal start_edit\nsignal end_edit\n\nconst NO_SHORTCUT = '<None>'\n\nvar _source_event = InputEventKey.new()\nvar _pre_edit_event = null\nvar _key_disp = NO_SHORTCUT\nvar _editing = false\n\nvar _modifier_keys = [KEY_ALT, KEY_CTRL, KEY_META, KEY_SHIFT]\n\n# Called when the node enters the scene tree for the first time.\nfunc _ready():\n\tset_process_unhandled_key_input(false)\n\n\nfunc _display_shortcut():\n\tif(_key_disp == ''):\n\t\t_key_disp = NO_SHORTCUT\n\t_ctrls.shortcut_label.text = _key_disp\n\n\nfunc _is_shift_only_modifier():\n\treturn _source_event.shift_pressed and \\\n\t\t!(_source_event.alt_pressed or \\\n\t\t\t_source_event.ctrl_pressed or \\\n\t\t\t_source_event.meta_pressed) \\\n\t\tand !_is_modifier(_source_event.keycode)\n\n\nfunc _has_modifier(event):\n\treturn event.alt_pressed or event.ctrl_pressed or \\\n\t\tevent.meta_pressed or event.shift_pressed\n\n\nfunc _is_modifier(keycode):\n\treturn _modifier_keys.has(keycode)\n\n\nfunc _edit_mode(should):\n\t_editing = should\n\tset_process_unhandled_key_input(should)\n\t_ctrls.set_button.visible = !should\n\t_ctrls.save_button.visible = should\n\t_ctrls.save_button.disabled = should\n\t_ctrls.cancel_button.visible = should\n\t_ctrls.clear_button.visible = !should\n\n\tif(should and to_s() == ''):\n\t\t_ctrls.shortcut_label.text = 'press buttons'\n\telse:\n\t\t_ctrls.shortcut_label.text = to_s()\n\n\tif(should):\n\t\temit_signal(\"start_edit\")\n\telse:\n\t\temit_signal(\"end_edit\")\n\n# ---------------\n# Events\n# ---------------\nfunc _unhandled_key_input(event):\n\tif(event is InputEventKey):\n\t\tif(event.pressed):\n\t\t\tif(_has_modifier(event) and !_is_modifier(event.get_keycode_with_modifiers())):\n\t\t\t\t_source_event = event\n\t\t\t\t_key_disp = OS.get_keycode_string(event.get_keycode_with_modifiers())\n\t\t\telse:\n\t\t\t\t_source_event = InputEventKey.new()\n\t\t\t\t_key_disp = NO_SHORTCUT\n\t\t\t_display_shortcut()\n\t\t\t_ctrls.save_button.disabled = !is_valid()\n\n\nfunc _on_SetButton_pressed():\n\t_pre_edit_event = _source_event.duplicate(true)\n\t_edit_mode(true)\n\n\nfunc _on_SaveButton_pressed():\n\t_edit_mode(false)\n\t_pre_edit_event = null\n\temit_signal('changed')\n\n\nfunc _on_CancelButton_pressed():\n\tcancel()\n\n\nfunc _on_ClearButton_pressed():\n\tclear_shortcut()\n\n# ---------------\n# Public\n# ---------------\nfunc to_s():\n\treturn OS.get_keycode_string(_source_event.get_keycode_with_modifiers())\n\n\nfunc is_valid():\n\treturn _has_modifier(_source_event) and !_is_shift_only_modifier()\n\n\nfunc get_shortcut():\n\tvar to_return = Shortcut.new()\n\tto_return.events.append(_source_event)\n\treturn to_return\n\nfunc get_input_event():\n\treturn _source_event\n\nfunc set_shortcut(sc):\n\tif(sc == null or sc.events == null || sc.events.size() <= 0):\n\t\tclear_shortcut()\n\telse:\n\t\t_source_event = sc.events[0]\n\t\t_key_disp = to_s()\n\t\t_display_shortcut()\n\n\nfunc clear_shortcut():\n\t_source_event = InputEventKey.new()\n\t_key_disp = NO_SHORTCUT\n\t_display_shortcut()\n\n\nfunc disable_set(should):\n\t_ctrls.set_button.disabled = should\n\n\nfunc disable_clear(should):\n\t_ctrls.clear_button.disabled = should\n\t\n\t\nfunc cancel():\n\tif(_editing):\n\t\t_edit_mode(false)\n\t\t_source_event = _pre_edit_event\n\t\t_key_disp = to_s()\n\t\t_display_shortcut()\n"
  },
  {
    "path": "demo/addons/gut/gui/ShortcutButton.gd.uid",
    "content": "uid://k6hvvpekp0xw\n"
  },
  {
    "path": "demo/addons/gut/gui/ShortcutButton.tscn",
    "content": "[gd_scene load_steps=2 format=3 uid=\"uid://sfb1fw8j6ufu\"]\n\n[ext_resource type=\"Script\" uid=\"uid://k6hvvpekp0xw\" path=\"res://addons/gut/gui/ShortcutButton.gd\" id=\"1\"]\n\n[node name=\"ShortcutButton\" type=\"Control\"]\ncustom_minimum_size = Vector2(210, 30)\nlayout_mode = 3\nanchor_right = 0.123\nanchor_bottom = 0.04\noffset_right = 68.304\noffset_bottom = 6.08\nscript = ExtResource(\"1\")\n\n[node name=\"Layout\" type=\"HBoxContainer\" parent=\".\"]\nlayout_mode = 0\nanchor_right = 1.0\nanchor_bottom = 1.0\n\n[node name=\"lblShortcut\" type=\"Label\" parent=\"Layout\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 7\ntext = \"<None>\"\nhorizontal_alignment = 2\n\n[node name=\"CenterContainer\" type=\"CenterContainer\" parent=\"Layout\"]\ncustom_minimum_size = Vector2(10, 0)\nlayout_mode = 2\n\n[node name=\"SetButton\" type=\"Button\" parent=\"Layout\"]\ncustom_minimum_size = Vector2(60, 0)\nlayout_mode = 2\ntext = \"Set\"\n\n[node name=\"SaveButton\" type=\"Button\" parent=\"Layout\"]\nvisible = false\ncustom_minimum_size = Vector2(60, 0)\nlayout_mode = 2\ntext = \"Save\"\n\n[node name=\"CancelButton\" type=\"Button\" parent=\"Layout\"]\nvisible = false\ncustom_minimum_size = Vector2(60, 0)\nlayout_mode = 2\ntext = \"Cancel\"\n\n[node name=\"ClearButton\" type=\"Button\" parent=\"Layout\"]\ncustom_minimum_size = Vector2(60, 0)\nlayout_mode = 2\ntext = \"Clear\"\n\n[connection signal=\"pressed\" from=\"Layout/SetButton\" to=\".\" method=\"_on_SetButton_pressed\"]\n[connection signal=\"pressed\" from=\"Layout/SaveButton\" to=\".\" method=\"_on_SaveButton_pressed\"]\n[connection signal=\"pressed\" from=\"Layout/CancelButton\" to=\".\" method=\"_on_CancelButton_pressed\"]\n[connection signal=\"pressed\" from=\"Layout/ClearButton\" to=\".\" method=\"_on_ClearButton_pressed\"]\n"
  },
  {
    "path": "demo/addons/gut/gui/ShortcutDialog.gd",
    "content": "@tool\nextends ConfirmationDialog\n\nvar GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')\nvar default_path = GutEditorGlobals.editor_shortcuts_path\n\n\n@onready var scbtn_run_all = $Scroll/Layout/CRunAll/ShortcutButton\n@onready var scbtn_run_current_script = $Scroll/Layout/CRunCurrentScript/ShortcutButton\n@onready var scbtn_run_current_inner = $Scroll/Layout/CRunCurrentInner/ShortcutButton\n@onready var scbtn_run_current_test = $Scroll/Layout/CRunCurrentTest/ShortcutButton\n@onready var scbtn_run_at_cursor = $Scroll/Layout/CRunAtCursor/ShortcutButton\n@onready var scbtn_rerun = $Scroll/Layout/CRerun/ShortcutButton\n@onready var scbtn_panel = $Scroll/Layout/CPanelButton/ShortcutButton\n@onready var scbtn_windowed = $Scroll/Layout/CToggleWindowed/ShortcutButton\n\n\n@onready var all_buttons = [\n\tscbtn_run_all, scbtn_run_current_script, scbtn_run_current_inner,\n\tscbtn_run_current_test, scbtn_run_at_cursor, scbtn_rerun,\n\tscbtn_panel, scbtn_windowed\n]\n\n\nfunc _debug_ready():\n\tpopup_centered()\n\n\tvar btn = Button.new()\n\tbtn.text = \"show\"\n\tget_tree().root.add_child(btn)\n\tbtn.pressed.connect(popup)\n\tbtn.position = Vector2(100, 100)\n\tbtn.size = Vector2(100, 100)\n\n\tsize_changed.connect(func(): title = str(size))\n\n\nfunc _ready():\n\tfor scbtn in all_buttons:\n\t\tscbtn.connect('start_edit', _on_edit_start.bind(scbtn))\n\t\tscbtn.connect('end_edit', _on_edit_end)\n\n\tcanceled.connect(_on_cancel)\n\n\t# Sizing this window on different monitors, especially compared to what it\n\t# looks like if you just run this project is annoying.  This is what I came\n\t# up with after getting annoyed.  You probably won't be looking at this\n\t# very often so it's fine...until it isn't.\n\tsize = Vector2(DisplayServer.screen_get_size()) * Vector2(.5, .8)\n\n\tif(get_parent() == get_tree().root):\n\t\t_debug_ready.call_deferred()\n\n\n\nfunc _cancel_all():\n\tfor scbtn in all_buttons:\n\t\tscbtn.cancel()\n\n\n# ------------\n# Events\n# ------------\nfunc _on_cancel():\n\t_cancel_all()\n\tload_shortcuts()\n\n\nfunc _on_edit_start(which):\n\tfor scbtn in all_buttons:\n\t\tif(scbtn != which):\n\t\t\tscbtn.disable_set(true)\n\t\t\tscbtn.disable_clear(true)\n\n\nfunc _on_edit_end():\n\tfor scbtn in all_buttons:\n\t\tscbtn.disable_set(false)\n\t\tscbtn.disable_clear(false)\n\n\n# ------------\n# Public\n# ------------\nfunc save_shortcuts():\n\tsave_shortcuts_to_file(default_path)\n\n\nfunc save_shortcuts_to_file(path):\n\tvar f = ConfigFile.new()\n\tf.set_value('main', 'panel_button', scbtn_panel.get_shortcut())\n\tf.set_value('main', 'rerun', scbtn_rerun.get_shortcut())\n\tf.set_value('main', 'run_all', scbtn_run_all.get_shortcut())\n\tf.set_value('main', 'run_at_cursor', scbtn_run_at_cursor.get_shortcut())\n\tf.set_value('main', 'run_current_inner', scbtn_run_current_inner.get_shortcut())\n\tf.set_value('main', 'run_current_script', scbtn_run_current_script.get_shortcut())\n\tf.set_value('main', 'run_current_test', scbtn_run_current_test.get_shortcut())\n\tf.set_value('main', 'toggle_windowed', scbtn_windowed.get_shortcut())\n\tf.save(path)\n\n\nfunc load_shortcuts():\n\tload_shortcuts_from_file(default_path)\n\n\nfunc load_shortcuts_from_file(path):\n\tvar f = ConfigFile.new()\n\t# as long as this shortcut is never modified, this is fine, otherwise\n\t# each thing should get its own default instead.\n\tvar empty = Shortcut.new()\n\n\tf.load(path)\n\tscbtn_panel.set_shortcut(f.get_value('main', 'panel_button', empty))\n\tscbtn_rerun.set_shortcut(f.get_value('main', 'rerun', empty))\n\tscbtn_run_all.set_shortcut(f.get_value('main', 'run_all', empty))\n\tscbtn_run_at_cursor.set_shortcut(f.get_value('main', 'run_at_cursor', empty))\n\tscbtn_run_current_inner.set_shortcut(f.get_value('main', 'run_current_inner', empty))\n\tscbtn_run_current_script.set_shortcut(f.get_value('main', 'run_current_script', empty))\n\tscbtn_run_current_test.set_shortcut(f.get_value('main', 'run_current_test', empty))\n\tscbtn_windowed.set_shortcut(f.get_value('main', 'toggle_windowed', empty))\n"
  },
  {
    "path": "demo/addons/gut/gui/ShortcutDialog.gd.uid",
    "content": "uid://dc5jgemxslgvl\n"
  },
  {
    "path": "demo/addons/gut/gui/ShortcutDialog.tscn",
    "content": "[gd_scene load_steps=3 format=3 uid=\"uid://dj5ve0bq7xa5j\"]\n\n[ext_resource type=\"Script\" uid=\"uid://dc5jgemxslgvl\" path=\"res://addons/gut/gui/ShortcutDialog.gd\" id=\"1_qq8qn\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://sfb1fw8j6ufu\" path=\"res://addons/gut/gui/ShortcutButton.tscn\" id=\"2_i3wie\"]\n\n[node name=\"ShortcutDialog\" type=\"ConfirmationDialog\"]\noversampling_override = 1.0\ntitle = \"GUT Shortcuts\"\nposition = Vector2i(0, 36)\nsize = Vector2i(1920, 1728)\nvisible = true\nscript = ExtResource(\"1_qq8qn\")\n\n[node name=\"Scroll\" type=\"ScrollContainer\" parent=\".\"]\noffset_left = 8.0\noffset_top = 8.0\noffset_right = 1912.0\noffset_bottom = 1679.0\n\n[node name=\"Layout\" type=\"VBoxContainer\" parent=\"Scroll\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\n\n[node name=\"ShortcutDescription\" type=\"RichTextLabel\" parent=\"Scroll/Layout\"]\ncustom_minimum_size = Vector2(0, 20)\nlayout_mode = 2\nbbcode_enabled = true\ntext = \"Shortcuts for:\n\t- Buttons in Panel\n\t- Project->Tools->GUT menu items\nShortcuts that only apply to menus are labeled.\"\nfit_content = true\nscroll_active = false\n\n[node name=\"TopPad\" type=\"CenterContainer\" parent=\"Scroll/Layout\"]\ncustom_minimum_size = Vector2(0, 5)\nlayout_mode = 2\n\n[node name=\"CPanelButton\" type=\"HBoxContainer\" parent=\"Scroll/Layout\"]\nlayout_mode = 2\n\n[node name=\"Label\" type=\"Label\" parent=\"Scroll/Layout/CPanelButton\"]\ncustom_minimum_size = Vector2(50, 0)\nlayout_mode = 2\nsize_flags_vertical = 7\ntext = \"Show/Hide GUT\"\n\n[node name=\"ShortcutButton\" parent=\"Scroll/Layout/CPanelButton\" instance=ExtResource(\"2_i3wie\")]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"ShortcutDescription2\" type=\"RichTextLabel\" parent=\"Scroll/Layout\"]\ncustom_minimum_size = Vector2(0, 20)\nlayout_mode = 2\nbbcode_enabled = true\ntext = \"[i]Show/hide the gut panel or move focus to/away from the GUT window.\n[/i]\"\nfit_content = true\nscroll_active = false\n\n[node name=\"CRunAll\" type=\"HBoxContainer\" parent=\"Scroll/Layout\"]\nlayout_mode = 2\n\n[node name=\"Label\" type=\"Label\" parent=\"Scroll/Layout/CRunAll\"]\ncustom_minimum_size = Vector2(50, 0)\nlayout_mode = 2\nsize_flags_vertical = 7\ntext = \"Run All\"\n\n[node name=\"ShortcutButton\" parent=\"Scroll/Layout/CRunAll\" instance=ExtResource(\"2_i3wie\")]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"ShortcutDescription3\" type=\"RichTextLabel\" parent=\"Scroll/Layout\"]\ncustom_minimum_size = Vector2(0, 20)\nlayout_mode = 2\nbbcode_enabled = true\ntext = \"[i]Run the entire test suite.[/i]\"\nfit_content = true\nscroll_active = false\n\n[node name=\"CRunCurrentScript\" type=\"HBoxContainer\" parent=\"Scroll/Layout\"]\nlayout_mode = 2\n\n[node name=\"Label\" type=\"Label\" parent=\"Scroll/Layout/CRunCurrentScript\"]\ncustom_minimum_size = Vector2(50, 0)\nlayout_mode = 2\nsize_flags_vertical = 7\ntext = \"Run Current Script\"\n\n[node name=\"ShortcutButton\" parent=\"Scroll/Layout/CRunCurrentScript\" instance=ExtResource(\"2_i3wie\")]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"ShortcutDescription4\" type=\"RichTextLabel\" parent=\"Scroll/Layout\"]\ncustom_minimum_size = Vector2(0, 20)\nlayout_mode = 2\nbbcode_enabled = true\ntext = \"[i]Run all tests in the currently selected script.[/i]\"\nfit_content = true\nscroll_active = false\n\n[node name=\"CRunCurrentInner\" type=\"HBoxContainer\" parent=\"Scroll/Layout\"]\nlayout_mode = 2\n\n[node name=\"Label\" type=\"Label\" parent=\"Scroll/Layout/CRunCurrentInner\"]\ncustom_minimum_size = Vector2(50, 0)\nlayout_mode = 2\nsize_flags_vertical = 7\ntext = \"Run Current Inner Class\"\n\n[node name=\"ShortcutButton\" parent=\"Scroll/Layout/CRunCurrentInner\" instance=ExtResource(\"2_i3wie\")]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"ShortcutDescription5\" type=\"RichTextLabel\" parent=\"Scroll/Layout\"]\ncustom_minimum_size = Vector2(0, 20)\nlayout_mode = 2\nbbcode_enabled = true\ntext = \"[i]Run only the currently selected inner test class if one is selected.[/i]\"\nfit_content = true\nscroll_active = false\n\n[node name=\"CRunCurrentTest\" type=\"HBoxContainer\" parent=\"Scroll/Layout\"]\nlayout_mode = 2\n\n[node name=\"Label\" type=\"Label\" parent=\"Scroll/Layout/CRunCurrentTest\"]\ncustom_minimum_size = Vector2(50, 0)\nlayout_mode = 2\nsize_flags_vertical = 7\ntext = \"Run Current Test\"\n\n[node name=\"ShortcutButton\" parent=\"Scroll/Layout/CRunCurrentTest\" instance=ExtResource(\"2_i3wie\")]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"ShortcutDescription6\" type=\"RichTextLabel\" parent=\"Scroll/Layout\"]\ncustom_minimum_size = Vector2(0, 20)\nlayout_mode = 2\nbbcode_enabled = true\ntext = \"[i]Run only the currently selected test, if one is selected[/i]\"\nfit_content = true\nscroll_active = false\n\n[node name=\"CRunAtCursor\" type=\"HBoxContainer\" parent=\"Scroll/Layout\"]\nlayout_mode = 2\n\n[node name=\"Label\" type=\"Label\" parent=\"Scroll/Layout/CRunAtCursor\"]\ncustom_minimum_size = Vector2(50, 0)\nlayout_mode = 2\nsize_flags_vertical = 7\ntext = \"Run At Cursor (menu only)\"\n\n[node name=\"ShortcutButton\" parent=\"Scroll/Layout/CRunAtCursor\" instance=ExtResource(\"2_i3wie\")]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"ShortcutDescription7\" type=\"RichTextLabel\" parent=\"Scroll/Layout\"]\ncustom_minimum_size = Vector2(0, 20)\nlayout_mode = 2\nbbcode_enabled = true\ntext = \"[i]Run the most specific test/inner class/script based on where the cursor is.[/i]\"\nfit_content = true\nscroll_active = false\n\n[node name=\"CRerun\" type=\"HBoxContainer\" parent=\"Scroll/Layout\"]\nlayout_mode = 2\n\n[node name=\"Label\" type=\"Label\" parent=\"Scroll/Layout/CRerun\"]\ncustom_minimum_size = Vector2(50, 0)\nlayout_mode = 2\nsize_flags_vertical = 7\ntext = \"Rerun (menu only)\"\n\n[node name=\"ShortcutButton\" parent=\"Scroll/Layout/CRerun\" instance=ExtResource(\"2_i3wie\")]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"ShortcutDescription8\" type=\"RichTextLabel\" parent=\"Scroll/Layout\"]\ncustom_minimum_size = Vector2(0, 20)\nlayout_mode = 2\nbbcode_enabled = true\ntext = \"[i]Rerun the test(s) that were last run.\"\nfit_content = true\nscroll_active = false\n\n[node name=\"CToggleWindowed\" type=\"HBoxContainer\" parent=\"Scroll/Layout\"]\nlayout_mode = 2\n\n[node name=\"Label\" type=\"Label\" parent=\"Scroll/Layout/CToggleWindowed\"]\ncustom_minimum_size = Vector2(50, 0)\nlayout_mode = 2\nsize_flags_vertical = 7\ntext = \"Toggle Windowed\"\n\n[node name=\"ShortcutButton\" parent=\"Scroll/Layout/CToggleWindowed\" instance=ExtResource(\"2_i3wie\")]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"ShortcutDescription9\" type=\"RichTextLabel\" parent=\"Scroll/Layout\"]\ncustom_minimum_size = Vector2(0, 20)\nlayout_mode = 2\nbbcode_enabled = true\ntext = \"[i]Toggle GUT in the bottom panel or a separate window.[/i]\"\nfit_content = true\nscroll_active = false\n"
  },
  {
    "path": "demo/addons/gut/gui/about.gd",
    "content": "@tool\nextends AcceptDialog\n\nvar GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')\n\nvar _bbcode = \\\n\"\"\"\n[center]GUT {gut_version}[/center]\n\n[center][b]GUT Links[/b]\n{gut_link_table}[/center]\n\n[center][b]VSCode Extension Links[/b]\n{vscode_link_table}[/center]\n\n[center]You can support GUT development at\n{donate_link}\n\nThanks for using GUT!\n[/center]\n\"\"\"\n\nvar _gut_links = [\n\t[&\"Documentation\", &\"https://gut.readthedocs.io\"],\n\t[&\"What's New\", &\"https://github.com/bitwes/Gut/releases/tag/v{gut_version}\"],\n\t[&\"Repo\", &\"https://github.com/bitwes/gut\"],\n\t[&\"Report Bugs\", &\"https://github.com/bitwes/gut/issues\"]\n]\n\nvar _vscode_links = [\n\t[\"Repo\", \"https://github.com/bitwes/gut-extension\"],\n\t[\"Market Place\", \"https://marketplace.visualstudio.com/items?itemName=bitwes.gut-extension\"]\n]\n\nvar _donate_link = \"https://buymeacoffee.com/bitwes\"\n\n@onready var _logo = $Logo\n\n\nfunc _ready():\n\tif(get_parent() is SubViewport):\n\t\treturn\n\n\t_vert_center_logo()\n\t$Logo.disabled = true\n\t$HBox/Scroll/RichTextLabel.text = _make_text()\n\n\nfunc _color_link(link_text):\n\treturn str(\"[color=ROYAL_BLUE]\", link_text, \"[/color]\")\n\n\nfunc _link_table(entries):\n\tvar text = ''\n\tfor entry in entries:\n\t\ttext += str(\"[cell][right]\", entry[0], \"[/right][/cell]\")\n\t\tvar link = str(\"[url]\", entry[1], \"[/url]\")\n\t\tif(entry[1].length() > 60):\n\t\t\tlink = str(\"[url=\", entry[1], \"]\", entry[1].substr(0, 50), \"...[/url]\")\n\n\t\ttext += str(\"[cell][left]\", _color_link(link), \"[/left][/cell]\\n\")\n\treturn str('[table=2]', text, '[/table]')\n\n\nfunc _make_text():\n\tvar gut_link_table = _link_table(_gut_links)\n\tvar vscode_link_table = _link_table(_vscode_links)\n\n\tvar text = _bbcode.format({\n\t\t\"gut_link_table\":gut_link_table,\n\t\t\"vscode_link_table\":vscode_link_table,\n\t\t\"donate_link\":_color_link(str('[url]', _donate_link, '[/url]')),\n\t\t\"gut_version\":GutUtils.version_numbers.gut_version,\n\t})\n\treturn text\n\n\nfunc _vert_center_logo():\n\t_logo.position.y = size.y / 2.0\n\n\n# -----------\n# Events\n# -----------\nfunc _on_rich_text_label_meta_clicked(meta: Variant) -> void:\n\tOS.shell_open(str(meta))\n\n\nfunc _on_mouse_entered() -> void:\n\tpass#_logo.active = true\n\n\nfunc _on_mouse_exited() -> void:\n\tpass#_logo.active = false\n\n\nvar _odd_ball_eyes_l = 1.1\nvar _odd_ball_eyes_r = .7\nfunc _on_rich_text_label_meta_hover_started(meta: Variant) -> void:\n\tif(meta == _gut_links[0][1]):\n\t\t_logo.set_eye_color(Color.RED)\n\telif(meta.find(\"releases/tag/\") > 0):\n\t\t_logo.set_eye_color(Color.GREEN)\n\telif(meta == _gut_links[2][1]):\n\t\t_logo.set_eye_color(Color.PURPLE)\n\telif(meta == _gut_links[3][1]):\n\t\t_logo.set_eye_scale(1.2)\n\telif(meta == _vscode_links[0][1]):\n\t\t_logo.set_eye_scale(.5, .5)\n\telif(meta == _vscode_links[1][1]):\n\t\t_logo.set_eye_scale(_odd_ball_eyes_l, _odd_ball_eyes_r)\n\t\tvar temp = _odd_ball_eyes_l\n\t\t_odd_ball_eyes_l = _odd_ball_eyes_r\n\t\t_odd_ball_eyes_r = temp\n\telif(meta == _donate_link):\n\t\t_logo.active = false\n\n\nfunc _on_rich_text_label_meta_hover_ended(meta: Variant) -> void:\n\tif(meta == _donate_link):\n\t\t_logo.active = true\n\n\nfunc _on_logo_pressed() -> void:\n\t_logo.disabled = !_logo.disabled\n"
  },
  {
    "path": "demo/addons/gut/gui/about.gd.uid",
    "content": "uid://g7qu8ihdt3pd\n"
  },
  {
    "path": "demo/addons/gut/gui/about.tscn",
    "content": "[gd_scene load_steps=5 format=3 uid=\"uid://dqbkylpsatcqm\"]\n\n[ext_resource type=\"Script\" uid=\"uid://g7qu8ihdt3pd\" path=\"res://addons/gut/gui/about.gd\" id=\"1_bg86c\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://bjkn8mhx2fmt1\" path=\"res://addons/gut/gui/GutLogo.tscn\" id=\"3_kpic4\"]\n\n[sub_resource type=\"StyleBoxFlat\" id=\"StyleBoxFlat_q8rky\"]\nbg_color = Color(0, 0, 0, 0.49803922)\n\n[sub_resource type=\"StyleBoxEmpty\" id=\"StyleBoxEmpty_kpic4\"]\n\n[node name=\"About\" type=\"AcceptDialog\"]\noversampling_override = 1.0\ntitle = \"About GUT\"\nposition = Vector2i(0, 36)\nsize = Vector2i(1500, 800)\nvisible = true\nmin_size = Vector2i(800, 800)\nscript = ExtResource(\"1_bg86c\")\n\n[node name=\"HBox\" type=\"HBoxContainer\" parent=\".\"]\noffset_left = 8.0\noffset_top = 8.0\noffset_right = 1492.0\noffset_bottom = 751.0\nalignment = 1\n\n[node name=\"MakeRoomForLogo\" type=\"CenterContainer\" parent=\"HBox\"]\ncustom_minimum_size = Vector2(200, 0)\nlayout_mode = 2\n\n[node name=\"Scroll\" type=\"ScrollContainer\" parent=\"HBox\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\n\n[node name=\"RichTextLabel\" type=\"RichTextLabel\" parent=\"HBox/Scroll\"]\nlayout_mode = 2\nsize_flags_horizontal = 3\nsize_flags_vertical = 3\ntheme_override_styles/normal = SubResource(\"StyleBoxFlat_q8rky\")\ntheme_override_styles/focus = SubResource(\"StyleBoxEmpty_kpic4\")\nbbcode_enabled = true\nfit_content = true\n\n[node name=\"Logo\" parent=\".\" instance=ExtResource(\"3_kpic4\")]\nmodulate = Color(0.74509805, 0.74509805, 0.74509805, 1)\nposition = Vector2(151, 265)\nscale = Vector2(0.8, 0.8)\nactive = true\ndisabled = true\n\n[connection signal=\"mouse_entered\" from=\".\" to=\".\" method=\"_on_mouse_entered\"]\n[connection signal=\"mouse_exited\" from=\".\" to=\".\" method=\"_on_mouse_exited\"]\n[connection signal=\"meta_clicked\" from=\"HBox/Scroll/RichTextLabel\" to=\".\" method=\"_on_rich_text_label_meta_clicked\"]\n[connection signal=\"meta_hover_ended\" from=\"HBox/Scroll/RichTextLabel\" to=\".\" method=\"_on_rich_text_label_meta_hover_ended\"]\n[connection signal=\"meta_hover_started\" from=\"HBox/Scroll/RichTextLabel\" to=\".\" method=\"_on_rich_text_label_meta_hover_started\"]\n[connection signal=\"pressed\" from=\"Logo\" to=\".\" method=\"_on_logo_pressed\"]\n"
  },
  {
    "path": "demo/addons/gut/gui/editor_globals.gd",
    "content": "@tool\n\nstatic var GutUserPreferences = load(\"res://addons/gut/gui/gut_user_preferences.gd\")\nstatic var temp_directory = 'user://gut_temp_directory'\n\nstatic var editor_run_gut_config_path = 'gut_editor_config.json':\n\t# This avoids having to use path_join wherever we want to reference this\n\t# path.  The value is not supposed to change.  Could it be a constant\n\t# instead?  Probably, but I didn't like repeating the directory part.\n\t# Do I like that this is a bit witty.  Absolutely.\n\tget: return temp_directory.path_join(editor_run_gut_config_path)\n\t# Should this print a message or something instead?  Probably, but then I'd\n\t# be repeating even more code than if this was just a constant.  So I didn't,\n\t# even though I wanted to make the message a easter eggish fun message.\n\t# I didn't, so this dumb comment will have to serve as the easter eggish fun.\n\tset(v):\n\t\tprint(\"Be sure to document your code.  Never trust comments.\")\n\n\nstatic var editor_run_bbcode_results_path = 'gut_editor.bbcode':\n\tget: return temp_directory.path_join(editor_run_bbcode_results_path)\n\tset(v): pass\n\n\nstatic var editor_run_json_results_path = 'gut_editor.json':\n\tget: return temp_directory.path_join(editor_run_json_results_path)\n\tset(v): pass\n\n\nstatic var editor_shortcuts_path = 'gut_editor_shortcuts.cfg' :\n\tget: return temp_directory.path_join(editor_shortcuts_path)\n\tset(v): pass\n\nstatic var run_externally_options_path = 'gut_editor_run_externally.cfg' :\n\tget: return temp_directory.path_join(run_externally_options_path)\n\tset(v): pass\n\nstatic var _user_prefs = null\nstatic var user_prefs = _user_prefs :\n\t# workaround not being able to reference EditorInterface when not in\n\t# the editor.  This shouldn't be referenced by anything not in the\n\t# editor.\n\tget:\n\t\tif(_user_prefs == null and Engine.is_editor_hint()):\n\t\t\t# This is sometimes used when not in the editor.  Avoid parser error\n\t\t\t# for EditorInterface.\n\t\t\t_user_prefs = GutUserPreferences.new(GutUtils.get_editor_interface().get_editor_settings())\n\t\treturn _user_prefs\nstatic var gut_plugin = null\n\nstatic func create_temp_directory():\n\tDirAccess.make_dir_recursive_absolute(temp_directory)\n\n\nstatic func is_being_edited_in_editor(which):\n\tif(!Engine.is_editor_hint()):\n\t\treturn false\n\n\tvar trav = which\n\tvar is_scene_root = false\n\tvar editor_root = which.get_tree().edited_scene_root\n\twhile(trav != null and !is_scene_root):\n\t\tis_scene_root = editor_root == trav\n\t\tif(!is_scene_root):\n\t\t\ttrav = trav.get_parent()\n\treturn is_scene_root\n"
  },
  {
    "path": "demo/addons/gut/gui/editor_globals.gd.uid",
    "content": "uid://cbi00ubn046c2\n"
  },
  {
    "path": "demo/addons/gut/gui/gut_config_gui.gd",
    "content": "var PanelControls = load(\"res://addons/gut/gui/panel_controls.gd\")\nvar GutConfig = load('res://addons/gut/gut_config.gd')\n\nconst DIRS_TO_LIST = 6\n\n# specific titles that we need to do stuff with\nvar _titles = {\n\tdirs = null\n}\n\nvar _cfg_ctrls = {}\nvar opt_maker = null\n\nfunc _init(cont):\n\topt_maker = GutUtils.OptionMaker.new(cont)\n\t_cfg_ctrls = opt_maker.controls\n\t# _base_container = cont\n\n\nfunc _add_save_load():\n\tvar ctrl = PanelControls.SaveLoadControl.new('Config', '', '')\n\n\tctrl.save_path_chosen.connect(_on_save_path_chosen)\n\tctrl.load_path_chosen.connect(_on_load_path_chosen)\n\n\t#_cfg_ctrls['save_load'] = ctrl\n\topt_maker.add_ctrl('save_load', ctrl)\n\treturn ctrl\n\n# ------------------\n# Events\n# ------------------\nfunc _on_save_path_chosen(path):\n\tsave_file(path)\n\n\nfunc _on_load_path_chosen(path):\n\tload_file.bind(path).call_deferred()\n\n# ------------------\n# Public\n# ------------------\nfunc get_config_issues():\n\tvar to_return = []\n\tvar has_directory = false\n\n\tfor i in range(DIRS_TO_LIST):\n\t\tvar key = str('directory_', i)\n\t\tvar path = _cfg_ctrls[key].value\n\t\tif(path != null and path != ''):\n\t\t\thas_directory = true\n\t\t\tif(!DirAccess.dir_exists_absolute(path)):\n\t\t\t\t_cfg_ctrls[key].mark_invalid(true)\n\t\t\t\tto_return.append(str('Test directory ', path, ' does not exist.'))\n\t\t\telse:\n\t\t\t\t_cfg_ctrls[key].mark_invalid(false)\n\t\telse:\n\t\t\t_cfg_ctrls[key].mark_invalid(false)\n\n\tif(!has_directory):\n\t\tto_return.append('You do not have any directories set.')\n\t\t_titles.dirs.mark_invalid(true)\n\telse:\n\t\t_titles.dirs.mark_invalid(false)\n\n\tif(!_cfg_ctrls.suffix.value.ends_with('.gd')):\n\t\t_cfg_ctrls.suffix.mark_invalid(true)\n\t\tto_return.append(\"Script suffix must end in '.gd'\")\n\telse:\n\t\t_cfg_ctrls.suffix.mark_invalid(false)\n\n\treturn to_return\n\n\nfunc clear():\n\topt_maker.clear()\n\n\nfunc save_file(path):\n\tvar gcfg = GutConfig.new()\n\tgcfg.options = get_options({})\n\tgcfg.save_file(path)\n\n\n\nfunc load_file(path):\n\tvar gcfg = GutConfig.new()\n\tgcfg.load_options(path)\n\tclear()\n\tset_options(gcfg.options)\n\n\n# --------------\n# SUPER dumb but VERY fun hack to hide settings.  The various _add methods will\n# return what they add.  If you want to hide it, just assign the result to this.\n# YES, I could have just put .visible at the end, but I didn't think of that\n# until just now, and this was fun, non-permanent and the .visible at the end\n# isn't as obvious as hide_this =\n#\n# Also, we can't just skip adding the controls because other things are looking\n# for them and things start to blow up if you don't add them.\nvar hide_this = null :\n\tset(val):\n\t\tval.visible = false\n\n# --------------\n\nfunc set_options(opts):\n\tvar options = opts.duplicate()\n\n\t# _add_title('Save/Load')\n\t_add_save_load()\n\n\topt_maker.add_title(\"Settings\")\n\topt_maker.add_number(\"log_level\", options.log_level, \"Log Level\", 0, 3,\n\t\t\"Detail level for log messages.\\n\" + \\\n\t\t\"\\t0: Errors and failures only.\\n\" + \\\n\t\t\"\\t1: Adds all test names + warnings + info\\n\" + \\\n\t\t\"\\t2: Shows all asserts\\n\" + \\\n\t\t\"\\t3: Adds more stuff probably, maybe not.\")\n\topt_maker.add_float(\"wait_log_delay\", options.wait_log_delay, \"Wait Log Delay\", 0.1, 0.0, 999.1,\n\t\t\"How long to wait before displaying 'Awaiting' messages.\")\n\topt_maker.add_boolean('ignore_pause', options.ignore_pause, 'Ignore Pause',\n\t\t\"Ignore calls to pause_before_teardown\")\n\topt_maker.add_boolean('hide_orphans', options.hide_orphans, 'Hide Orphans',\n\t\t'Do not display orphan counts in output.')\n\topt_maker.add_boolean('should_exit', options.should_exit, 'Exit on Finish',\n\t\t\"Exit when tests finished.\")\n\topt_maker.add_boolean('should_exit_on_success', options.should_exit_on_success, 'Exit on Success',\n\t\t\"Exit if there are no failures.  Does nothing if 'Exit on Finish' is enabled.\")\n\topt_maker.add_select('double_strategy', 'Script Only', ['Include Native', 'Script Only'], 'Double Strategy',\n\t\t'\"Include Native\" will include native methods in Doubles.  \"Script Only\" will not.  ' + \"\\n\" + \\\n\t\t'The native method override warning is disabled when creating Doubles.' + \"\\n\" + \\\n\t\t'This is the default, you can override this at the script level or when creating doubles.')\n\t_cfg_ctrls.double_strategy.value = GutUtils.get_enum_value(\n\t\toptions.double_strategy, GutUtils.DOUBLE_STRATEGY, GutUtils.DOUBLE_STRATEGY.SCRIPT_ONLY)\n\n\n\topt_maker.add_title(\"Fail Error Types\")\n\topt_maker.add_boolean(\"error_tracking\", !options.no_error_tracking, 'Track Errors',\n\t\t\"Enable/Disable GUT's ability to detect engine and push errors.\")\n\topt_maker.add_boolean('engine_errors_cause_failure', options.failure_error_types.has(GutConfig.FAIL_ERROR_TYPE_ENGINE),\n\t\t'Engine', 'Any script/engine error that occurs during a test will cause the test to fail.')\n\topt_maker.add_boolean('push_error_errors_cause_failure', options.failure_error_types.has(GutConfig.FAIL_ERROR_TYPE_PUSH_ERROR),\n\t\t'Push', 'Any error generated by a call to push_error that occurs during a test will cause the test to fail.')\n\topt_maker.add_boolean('gut_errors_cause_failure', options.failure_error_types.has(GutConfig.FAIL_ERROR_TYPE_GUT),\n\t\t'GUT', 'Any internal GUT error that occurs while a test is running will cause it to fail..')\n\n\n\topt_maker.add_title('Runner Appearance')\n\thide_this = opt_maker.add_boolean(\"gut_on_top\", options.gut_on_top, \"On Top\",\n\t\t\"The GUT Runner appears above children added during tests.\")\n\topt_maker.add_number('opacity', options.opacity, 'Opacity', 0, 100,\n\t\t\"The opacity of GUT when tests are running.\")\n\thide_this = opt_maker.add_boolean('should_maximize', options.should_maximize, 'Maximize',\n\t\t\"Maximize GUT when tests are being run.\")\n\topt_maker.add_boolean('compact_mode', options.compact_mode, 'Compact Mode',\n\t\t'The runner will be in compact mode.  This overrides Maximize.')\n\topt_maker.add_select('font_name', options.font_name, GutUtils.avail_fonts, 'Font',\n\t\t\"The font to use for text output in the Gut Runner.\")\n\topt_maker.add_number('font_size', options.font_size, 'Font Size', 5, 100,\n\t\t\"The font size for text output in the Gut Runner.\")\n\thide_this = opt_maker.add_color('font_color', options.font_color, 'Font Color',\n\t\t\"The font color for text output in the Gut Runner.\")\n\topt_maker.add_color('background_color', options.background_color, 'Background Color',\n\t\t\"The background color for text output in the Gut Runner.\")\n\topt_maker.add_boolean('disable_colors', options.disable_colors, 'Disable Formatting',\n\t\t'Disable formatting and colors used in the Runner.  Does not affect panel output.')\n\n\n\t_titles.dirs = opt_maker.add_title('Test Directories')\n\topt_maker.add_boolean('include_subdirs', options.include_subdirs, 'Include Subdirs',\n\t\t\"Include subdirectories of the directories configured below.\")\n\n\tvar dirs_to_load = options.configured_dirs\n\tif(options.dirs.size() > dirs_to_load.size()):\n\t\tdirs_to_load = options.dirs\n\n\tfor i in range(DIRS_TO_LIST):\n\t\tvar value = ''\n\t\tif(dirs_to_load.size() > i):\n\t\t\tvalue = dirs_to_load[i]\n\n\t\tvar test_dir = opt_maker.add_directory(str('directory_', i), value, str(i))\n\t\ttest_dir.enabled_button.visible = true\n\t\ttest_dir.enabled_button.button_pressed = options.dirs.has(value)\n\n\n\topt_maker.add_title(\"XML Output\")\n\topt_maker.add_save_file_anywhere(\"junit_xml_file\", options.junit_xml_file, \"Output Path\",\n\t\t\"Path and filename where GUT should create a JUnit compliant XML file.  \" +\n\t\t\"This file will contain the results of the last test run.  To avoid \" +\n\t\t\"overriding the file use Include Timestamp.\")\n\topt_maker.add_boolean(\"junit_xml_timestamp\", options.junit_xml_timestamp, \"Include Timestamp\",\n\t\t\"Include a timestamp in the filename so that each run gets its own xml file.\")\n\n\n\topt_maker.add_title('Hooks')\n\topt_maker.add_file('pre_run_script', options.pre_run_script, 'Pre-Run Hook',\n\t\t'This script will be run by GUT before any tests are run.')\n\topt_maker.add_file('post_run_script', options.post_run_script, 'Post-Run Hook',\n\t\t'This script will be run by GUT after all tests are run.')\n\n\n\topt_maker.add_title('Misc')\n\topt_maker.add_value('prefix', options.prefix, 'Script Prefix',\n\t\t\"The filename prefix for all test scripts.\")\n\topt_maker.add_value('suffix', options.suffix, 'Script Suffix',\n\t\t\"Script suffix, including .gd extension.  For example '_foo.gd'.\")\n\topt_maker.add_float('paint_after', options.paint_after, 'Paint After', .05, 0.0, 1.0,\n\t\t\"How long GUT will wait before pausing for 1 frame to paint the screen.  0 is never.\")\n\n\n\nfunc get_options(base_opts):\n\tvar to_return = base_opts.duplicate()\n\n\t# Settings\n\tto_return.log_level = _cfg_ctrls.log_level.value\n\tto_return.wait_log_delay = _cfg_ctrls.wait_log_delay.value\n\tto_return.ignore_pause = _cfg_ctrls.ignore_pause.value\n\tto_return.hide_orphans = _cfg_ctrls.hide_orphans.value\n\tto_return.should_exit = _cfg_ctrls.should_exit.value\n\tto_return.should_exit_on_success = _cfg_ctrls.should_exit_on_success.value\n\tto_return.double_strategy = _cfg_ctrls.double_strategy.value\n\n\t# Runner Appearance\n\tto_return.font_name = _cfg_ctrls.font_name.text\n\tto_return.font_size = _cfg_ctrls.font_size.value\n\tto_return.should_maximize = _cfg_ctrls.should_maximize.value\n\tto_return.compact_mode = _cfg_ctrls.compact_mode.value\n\tto_return.opacity = _cfg_ctrls.opacity.value\n\tto_return.background_color = _cfg_ctrls.background_color.value.to_html()\n\tto_return.font_color = _cfg_ctrls.font_color.value.to_html()\n\tto_return.disable_colors = _cfg_ctrls.disable_colors.value\n\tto_return.gut_on_top = _cfg_ctrls.gut_on_top.value\n\tto_return.paint_after = _cfg_ctrls.paint_after.value\n\n\t# Fail Error Types\n\tto_return.no_error_tracking = !_cfg_ctrls.error_tracking\n\n\tvar fail_error_types = []\n\tif(_cfg_ctrls.engine_errors_cause_failure.value):\n\t\tfail_error_types.append(GutConfig.FAIL_ERROR_TYPE_ENGINE)\n\tif(_cfg_ctrls.push_error_errors_cause_failure.value):\n\t\tfail_error_types.append(GutConfig.FAIL_ERROR_TYPE_PUSH_ERROR)\n\tif(_cfg_ctrls.gut_errors_cause_failure.value):\n\t\tfail_error_types.append(GutConfig.FAIL_ERROR_TYPE_GUT)\n\tto_return.failure_error_types = fail_error_types\n\n\t# Directories\n\tto_return.include_subdirs = _cfg_ctrls.include_subdirs.value\n\tvar dirs = []\n\tvar configured_dirs = []\n\tfor i in range(DIRS_TO_LIST):\n\t\tvar key = str('directory_', i)\n\t\tvar ctrl = _cfg_ctrls[key]\n\t\tif(ctrl.value != '' and ctrl.value != null):\n\t\t\tconfigured_dirs.append(ctrl.value)\n\t\t\tif(ctrl.enabled_button.button_pressed):\n\t\t\t\tdirs.append(ctrl.value)\n\tto_return.dirs = dirs\n\tto_return.configured_dirs = configured_dirs\n\n\t# XML Output\n\tto_return.junit_xml_file = _cfg_ctrls.junit_xml_file.value\n\tto_return.junit_xml_timestamp = _cfg_ctrls.junit_xml_timestamp.value\n\n\t# Hooks\n\tto_return.pre_run_script = _cfg_ctrls.pre_run_script.value\n\tto_return.post_run_script = _cfg_ctrls.post_run_script.value\n\n\t# Misc\n\tto_return.prefix = _cfg_ctrls.prefix.value\n\tto_return.suffix = _cfg_ctrls.suffix.value\n\n\treturn to_return\n\n\nfunc mark_saved():\n\tfor key in _cfg_ctrls:\n\t\t_cfg_ctrls[key].mark_unsaved(false)\n"
  },
  {
    "path": "demo/addons/gut/gui/gut_config_gui.gd.uid",
    "content": "uid://chosc1tvfaduq\n"
  },
  {
    "path": "demo/addons/gut/gui/gut_gui.gd",
    "content": "extends Control\n# ##############################################################################\n# This is the decoupled GUI for gut.gd\n#\n# This is a \"generic\" interface between a GUI and gut.gd.  It assumes there are\n# certain controls with specific names.  It will then interact with those\n# controls based on signals emitted from gut.gd in order to give the user\n# feedback about the progress of the test run and the results.\n#\n# Optional controls are marked as such in the _ctrls dictionary.  The names\n# of the controls can be found in _populate_ctrls.\n# ##############################################################################\nvar _gut = null\n\nvar _ctrls = {\n\tbtn_continue = null,\n\tpath_dir = null,\n\tpath_file = null,\n\tprog_script = null,\n\tprog_test = null,\n\trtl = null,                 # optional\n\trtl_bg = null,              # required if rtl exists\n\tswitch_modes = null,\n\ttime_label = null,\n\ttitle = null,\n\ttitle_bar = null,\n\ttgl_word_wrap = null,\t\t# optional\n}\n\nvar _title_mouse = {\n\tdown = false\n}\n\n\nsignal switch_modes()\n\nvar _max_position = Vector2(100, 100)\n\nfunc _ready():\n\t_populate_ctrls()\n\n\t_ctrls.btn_continue.visible = false\n\t_ctrls.btn_continue.pressed.connect(_on_continue_pressed)\n\t_ctrls.switch_modes.pressed.connect(_on_switch_modes_pressed)\n\t_ctrls.title_bar.gui_input.connect(_on_title_bar_input)\n\tif(_ctrls.tgl_word_wrap != null):\n\t\t_ctrls.tgl_word_wrap.toggled.connect(_on_word_wrap_toggled)\n\n\t_ctrls.prog_script.value = 0\n\t_ctrls.prog_test.value = 0\n\t_ctrls.path_dir.text = ''\n\t_ctrls.path_file.text = ''\n\t_ctrls.time_label.text = ''\n\n\t_max_position = get_display_size() - Vector2(30, _ctrls.title_bar.size.y)\n\n\nfunc _process(_delta):\n\tif(_gut != null and _gut.is_running()):\n\t\tset_elapsed_time(_gut.get_elapsed_time())\n\n\n# ------------------\n# Private\n# ------------------\nfunc get_display_size():\n\treturn get_viewport().get_visible_rect().size\n\n\nfunc _populate_ctrls():\n\t# Brute force, but flexible.  This allows for all the controls to exist\n\t# anywhere, and as long as they all have the right name, they will be\n\t# found.\n\t_ctrls.btn_continue = _get_first_child_named('Continue', self)\n\t_ctrls.path_dir = _get_first_child_named('Path', self)\n\t_ctrls.path_file = _get_first_child_named('File', self)\n\t_ctrls.prog_script = _get_first_child_named('ProgressScript', self)\n\t_ctrls.prog_test = _get_first_child_named('ProgressTest', self)\n\t_ctrls.rtl = _get_first_child_named('TestOutput', self)\n\t_ctrls.rtl_bg = _get_first_child_named('OutputBG', self)\n\t_ctrls.switch_modes = _get_first_child_named(\"SwitchModes\", self)\n\t_ctrls.time_label = _get_first_child_named('TimeLabel', self)\n\t_ctrls.title = _get_first_child_named(\"Title\", self)\n\t_ctrls.title_bar = _get_first_child_named(\"TitleBar\", self)\n\t_ctrls.tgl_word_wrap = _get_first_child_named(\"WordWrap\", self)\n\n\nfunc _get_first_child_named(obj_name, parent_obj):\n\tif(parent_obj == null):\n\t\treturn null\n\n\tvar kids = parent_obj.get_children()\n\tvar index = 0\n\tvar to_return = null\n\n\twhile(index < kids.size() and to_return == null):\n\t\tif(str(kids[index]).find(str(obj_name, ':')) != -1):\n\t\t\tto_return = kids[index]\n\t\telse:\n\t\t\tto_return = _get_first_child_named(obj_name, kids[index])\n\t\t\tif(to_return == null):\n\t\t\t\tindex += 1\n\t\n\treturn to_return\n\n\n\n# ------------------\n# Events\n# ------------------\nfunc _on_title_bar_input(event : InputEvent):\n\tif(event is InputEventMouseMotion):\n\t\tif(_title_mouse.down):\n\t\t\tposition += event.relative\n\t\t\tposition.x = clamp(position.x, 0, _max_position.x)\n\t\t\tposition.y = clamp(position.y, 0, _max_position.y)\n\telif(event is InputEventMouseButton):\n\t\tif(event.button_index == MOUSE_BUTTON_LEFT):\n\t\t\t_title_mouse.down = event.pressed\n\n\nfunc _on_continue_pressed():\n\t_gut.end_teardown_pause()\n\n\nfunc _on_gut_start_run():\n\tif(_ctrls.rtl != null):\n\t\t_ctrls.rtl.clear()\n\tset_num_scripts(_gut.get_test_collector().scripts.size())\n\n\nfunc _on_gut_end_run():\n\t_ctrls.prog_test.value = _ctrls.prog_test.max_value\n\t_ctrls.prog_script.value = _ctrls.prog_script.max_value\n\n\nfunc _on_gut_start_script(script_obj):\n\tnext_script(script_obj.get_full_name(), script_obj.tests.size())\n\n\nfunc _on_gut_end_script():\n\tpass\n\n\nfunc _on_gut_start_test(test_name):\n\tnext_test(test_name)\n\n\nfunc _on_gut_end_test():\n\tpass\n\n\nfunc _on_gut_start_pause():\n\tpause_before_teardown()\n\n\nfunc _on_gut_end_pause():\n\t_ctrls.btn_continue.visible = false\n\n\nfunc _on_switch_modes_pressed():\n\tswitch_modes.emit()\n\n\nfunc _on_word_wrap_toggled(toggled):\n\t_ctrls.rtl.autowrap_mode = toggled\n# ------------------\n# Public\n# ------------------\nfunc set_num_scripts(val):\n\t_ctrls.prog_script.value = 0\n\t_ctrls.prog_script.max_value = val\n\n\nfunc next_script(path, num_tests):\n\t_ctrls.prog_script.value += 1\n\t_ctrls.prog_test.value = 0\n\t_ctrls.prog_test.max_value = num_tests\n\n\t_ctrls.path_dir.text = path.get_base_dir()\n\t_ctrls.path_file.text = path.get_file()\n\n\nfunc next_test(__test_name):\n\t_ctrls.prog_test.value += 1\n\n\nfunc pause_before_teardown():\n\t_ctrls.btn_continue.visible = true\n\n\nfunc set_gut(g):\n\tif(_gut == g):\n\t\treturn\n\t_gut = g\n\tg.start_run.connect(_on_gut_start_run)\n\tg.end_run.connect(_on_gut_end_run)\n\n\tg.start_script.connect(_on_gut_start_script)\n\tg.end_script.connect(_on_gut_end_script)\n\n\tg.start_test.connect(_on_gut_start_test)\n\tg.end_test.connect(_on_gut_end_test)\n\n\tg.start_pause_before_teardown.connect(_on_gut_start_pause)\n\tg.end_pause_before_teardown.connect(_on_gut_end_pause)\n\nfunc get_gut():\n\treturn _gut\n\nfunc get_textbox():\n\treturn _ctrls.rtl\n\nfunc set_elapsed_time(t):\n\t_ctrls.time_label.text = str(\"%6.1f\" % t, 's')\n\n\nfunc set_bg_color(c):\n\t_ctrls.rtl_bg.color = c\n\n\nfunc set_title(text):\n\t_ctrls.title.text = text\n\n\nfunc to_top_left():\n\tself.position = Vector2(5, 5)\n\n\nfunc to_bottom_right():\n\tvar win_size = get_display_size()\n\tself.position = win_size - Vector2(self.size) - Vector2(5, 5)\n\n\nfunc align_right():\n\tvar win_size = get_display_size()\n\tself.position.x = win_size.x - self.size.x -5\n\tself.position.y = 5\n\tself.size.y = win_size.y - 10\n"
  },
  {
    "path": "demo/addons/gut/gui/gut_gui.gd.uid",
    "content": "uid://blvhsbnsvfyow\n"
  },
  {
    "path": "demo/addons/gut/gui/gut_logo.gd",
    "content": "@tool\nextends Node2D\n\nclass Eyeball:\n\textends Node2D\n\n\tvar _should_draw_laser = false\n\tvar _laser_end_pos = Vector2.ZERO\n\tvar _laser_timer : Timer = null\n\tvar _color_tween : Tween\n\tvar _size_tween : Tween\n\n\tvar sprite : Sprite2D = null\n\tvar default_position = Vector2(0, 0)\n\tvar move_radius = 25\n\tvar move_center = Vector2(0, 0)\n\tvar default_color = Color(0.31, 0.31, 0.31)\n\tvar _color = default_color :\n\t\tset(val):\n\t\t\t_color = val\n\t\t\tqueue_redraw()\n\tvar color = _color :\n\t\tset(val):\n\t\t\t_start_color_tween(_color, val)\n\t\tget(): return _color\n\tvar default_size = 70\n\tvar _size = default_size :\n\t\tset(val):\n\t\t\t_size = val\n\t\t\tqueue_redraw()\n\tvar size = _size :\n\t\tset(val):\n\t\t\t_start_size_tween(_size, val)\n\t\tget(): return _size\n\n\n\tfunc _init(node):\n\t\tsprite = node\n\t\tdefault_position = sprite.position\n\t\tmove_center = sprite.position\n\t\t# hijack the original sprite, because I want to draw it here but keep\n\t\t# the original in the scene for layout.\n\t\tposition = sprite.position\n\t\tsprite.get_parent().add_child(self)\n\t\tsprite.visible = false\n\n\n\tfunc _ready():\n\t\t_laser_timer = Timer.new()\n\t\t_laser_timer.wait_time = .1\n\t\t_laser_timer.one_shot = true\n\t\tadd_child(_laser_timer)\n\t\t_laser_timer.timeout.connect(func():  _should_draw_laser = false)\n\n\n\tfunc _process(_delta):\n\t\tif(_should_draw_laser):\n\t\t\tqueue_redraw()\n\n\n\tfunc _start_color_tween(old_color, new_color):\n\t\tif(_color_tween != null and _color_tween.is_running()):\n\t\t\t_color_tween.kill()\n\t\t_color_tween = create_tween()\n\t\t_color_tween.tween_property(self, '_color', new_color, .3).from(old_color)\n\t\t_color_tween.play()\n\n\n\tfunc _start_size_tween(old_size, new_size):\n\t\tif(_size_tween != null and _size_tween.is_running()):\n\t\t\t_size_tween.kill()\n\t\t_size_tween = create_tween()\n\t\t_size_tween.tween_property(self, '_size', new_size, .3).from(old_size)\n\t\t_size_tween.play()\n\n\n\tvar _laser_size = 20.0\n\tfunc _draw() -> void:\n\t\tdraw_circle(Vector2.ZERO, size, color, true, -1, true)\n\t\tif(_should_draw_laser):\n\t\t\tvar end_pos = (_laser_end_pos - global_position) * 2\n\t\t\tvar laser_size = _laser_size * (float(size)/float(default_size))\n\t\t\tdraw_line(Vector2.ZERO, end_pos, color, laser_size)\n\t\t\tdraw_line(Vector2.ZERO, end_pos, Color(1, 1, 1, .5), laser_size * .8)\n\n\n\t# There's a bug in here where the eye shakes like crazy.  It's a feature\n\t# now.  Don't fix it.\n\tfunc look_at_local_position(local_pos):\n\t\tvar dir = position.direction_to(local_pos)\n\t\tvar dist = position.distance_to(local_pos)\n\t\tposition = move_center + (dir * min(dist, move_radius))\n\t\tposition.x = clamp(position.x, move_center.x - move_radius, move_center.x + move_radius)\n\t\tposition.y = clamp(position.y, move_center.y - move_radius, move_center.y + move_radius)\n\n\n\tfunc reset():\n\t\tcolor = default_color\n\t\tsize = default_size\n\n\n\tfunc eye_laser(global_pos):\n\t\t_should_draw_laser = true\n\t\t_laser_end_pos = global_pos\n\t\t_laser_timer.start()\n\n\n\tfunc _stop_laser():\n\t\t_should_draw_laser = false\n\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nvar GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')\n# Active means it's actively doing stuff.  When this is not active the eyes\n# won't follow, but you can still make the sizes change by calling methods on\n# this.\n@export var active = false :\n\tset(val):\n\t\tactive = val\n\t\tif(!active and is_inside_tree()):\n\t\t\tleft_eye.position = left_eye.default_position\n\t\t\tright_eye.position = right_eye.default_position\n# When disabled, this will reset to default and you can't make it do anything.\n@export var disabled = false :\n\tset(val):\n\t\tdisabled = val\n\t\tif(disabled and is_inside_tree()):\n\t\t\tleft_eye.position = left_eye.default_position\n\t\t\tright_eye.position = right_eye.default_position\n\t\t\tleft_eye.reset()\n\t\t\tright_eye.reset()\n\t\t\tmodulate = Color.GRAY\n\t\t\t$BaseLogo.texture = _no_shine\n\t\telse:\n\t\t\t$BaseLogo.texture = _normal\n\t\t\tmodulate = Color.WHITE\n\n@onready var _reset_timer = $ResetTimer\n@onready var _face_button = $FaceButton\n@onready var left_eye : Eyeball = Eyeball.new($BaseLogo/LeftEye)\n@onready var right_eye : Eyeball = Eyeball.new($BaseLogo/RightEye)\n\nvar _no_shine = load(\"res://addons/gut/images/GutIconV2_no_shine.png\")\nvar _normal = load(\"res://addons/gut/images/GutIconV2_base.png\")\nvar _is_in_edited_scene = false\n\nsignal pressed\n\nfunc _debug_ready():\n\tposition = Vector2(500, 500)\n\tactive = true\n\n\nfunc _ready():\n\t_is_in_edited_scene = GutEditorGlobals.is_being_edited_in_editor(self)\n\n\tif(get_parent() == get_tree().root):\n\t\t_debug_ready()\n\n\tdisabled = disabled\n\tactive = active\n\tleft_eye.move_center.x -= 20\n\tright_eye.move_center.x += 10\n\t_face_button.modulate.a = 0.0\n\n\nfunc _process(_delta):\n\tif(active and !disabled and !_is_in_edited_scene):\n\t\tleft_eye.look_at_local_position(get_local_mouse_position())\n\t\tright_eye.look_at_local_position(get_local_mouse_position())\n\n\n# ----------------\n# Events\n# ----------------\nfunc _on_reset_timer_timeout() -> void:\n\tleft_eye.reset()\n\tright_eye.reset()\n\n\nfunc _on_face_button_pressed() -> void:\n\tpressed.emit()\n\n\n# ----------------\n# Public\n# ----------------\nfunc set_eye_scale(left, right=left):\n\tif(disabled or _is_in_edited_scene):\n\t\treturn\n\tleft_eye.size = left_eye.default_size * left\n\tright_eye.size = right_eye.default_size * right\n\t_reset_timer.start()\n\n\nfunc reset_eye_size():\n\tif(disabled or _is_in_edited_scene):\n\t\treturn\n\tleft_eye.size = left_eye.default_size\n\tright_eye.size = right_eye.default_size\n\n\nfunc set_eye_color(left, right=left):\n\tif(disabled or _is_in_edited_scene):\n\t\treturn\n\tleft_eye.color = left\n\tright_eye.color = right\n\t_reset_timer.start()\n\n\nfunc reset_eye_color():\n\tif(disabled or _is_in_edited_scene):\n\t\treturn\n\tleft_eye.color = left_eye.default_color\n\tright_eye.color = right_eye.default_color\n\n\n# I removed the eye lasers because they aren't ready yet.  I've already spent\n# too much time on this logo.  It's great, I love it...but it's been long\n# enough.  This gives me, or someone else, something to do later.\n#func eye_lasers(global_pos):\n\t#left_eye.eye_laser(global_pos)\n\t#right_eye.eye_laser(global_pos)\n"
  },
  {
    "path": "demo/addons/gut/gui/gut_logo.gd.uid",
    "content": "uid://b8lvgepb64m8t\n"
  },
  {
    "path": "demo/addons/gut/gui/gut_user_preferences.gd",
    "content": "class GutEditorPref:\n\tvar gut_pref_prefix = 'gut/'\n\tvar pname = '__not_set__'\n\tvar default = null\n\tvar value = '__not_set__'\n\tvar _settings = null\n\n\tfunc _init(n, d, s):\n\t\tpname = n\n\t\tdefault = d\n\t\t_settings = s\n\t\tload_it()\n\n\tfunc _prefstr():\n\t\tvar to_return = str(gut_pref_prefix, pname)\n\t\treturn to_return\n\n\tfunc save_it():\n\t\t_settings.set_setting(_prefstr(), value)\n\n\tfunc load_it():\n\t\tif(_settings.has_setting(_prefstr())):\n\t\t\tvalue = _settings.get_setting(_prefstr())\n\t\telse:\n\t\t\tvalue = default\n\n\tfunc erase():\n\t\t_settings.erase(_prefstr())\n\n\nconst EMPTY = '-- NOT_SET --'\n\n# -- Editor ONLY Settings --\nvar output_font_name = null\nvar output_font_size = null\nvar hide_result_tree = null\nvar hide_output_text = null\nvar hide_settings = null\nvar use_colors = null\t# ? might be output panel\nvar run_externally = null\nvar run_externally_options_dialog_size = null\nvar shortcuts_dialog_size = null\nvar gut_window_size = null\nvar gut_window_on_top = null\n\n\nfunc _init(editor_settings):\n\toutput_font_name = GutEditorPref.new('output_font_name', 'CourierPrime', editor_settings)\n\toutput_font_size = GutEditorPref.new('output_font_size', 30, editor_settings)\n\thide_result_tree = GutEditorPref.new('hide_result_tree', false, editor_settings)\n\thide_output_text = GutEditorPref.new('hide_output_text', false, editor_settings)\n\thide_settings = GutEditorPref.new('hide_settings', false, editor_settings)\n\tuse_colors = GutEditorPref.new('use_colors', true, editor_settings)\n\trun_externally = GutEditorPref.new('run_externally', false, editor_settings)\n\trun_externally_options_dialog_size = GutEditorPref.new('run_externally_options_dialog_size', Vector2i(-1, -1), editor_settings)\n\tshortcuts_dialog_size = GutEditorPref.new('shortcuts_dialog_size', Vector2i(-1, -1), editor_settings)\n\tgut_window_size = GutEditorPref.new('editor_window_size', Vector2i(-1, -1), editor_settings)\n\tgut_window_on_top = GutEditorPref.new('editor_window_on_top', false, editor_settings)\n\n\nfunc save_it():\n\tfor prop in get_property_list():\n\t\tvar val = get(prop.name)\n\t\tif(val is GutEditorPref):\n\t\t\tval.save_it()\n\n\nfunc load_it():\n\tfor prop in get_property_list():\n\t\tvar val = get(prop.name)\n\t\tif(val is GutEditorPref):\n\t\t\tval.load_it()\n\n\nfunc erase_all():\n\tfor prop in get_property_list():\n\t\tvar val = get(prop.name)\n\t\tif(val is GutEditorPref):\n\t\t\tval.erase()\n"
  },
  {
    "path": "demo/addons/gut/gui/gut_user_preferences.gd.uid",
    "content": "uid://dsndkn6whyiov\n"
  },
  {
    "path": "demo/addons/gut/gui/option_maker.gd",
    "content": "var PanelControls = load(\"res://addons/gut/gui/panel_controls.gd\")\n\n# All titles so we can free them when we want.\nvar _all_titles = []\n\n\nvar base_container = null\n# All the various PanelControls indexed by thier keys.\nvar controls = {}\n\n\nfunc _init(cont):\n\tbase_container = cont\n\n\nfunc add_title(text):\n\tvar row = PanelControls.BaseGutPanelControl.new(text, text)\n\tbase_container.add_child(row)\n\trow.connect('draw', _on_title_cell_draw.bind(row))\n\t_all_titles.append(row)\n\treturn row\n\n\nfunc add_ctrl(key, ctrl):\n\tcontrols[key] = ctrl\n\tbase_container.add_child(ctrl)\n\n\nfunc add_number(key, value, disp_text, v_min, v_max, hint=''):\n\tvar ctrl = PanelControls.NumberControl.new(disp_text, value, v_min, v_max, hint)\n\tadd_ctrl(key, ctrl)\n\treturn ctrl\n\n\nfunc add_float(key, value, disp_text, step, v_min, v_max, hint=''):\n\tvar ctrl = PanelControls.FloatControl.new(disp_text, value, step, v_min, v_max, hint)\n\tadd_ctrl(key, ctrl)\n\treturn ctrl\n\n\nfunc add_select(key, value, values, disp_text, hint=''):\n\tvar ctrl = PanelControls.SelectControl.new(disp_text, value, values, hint)\n\tadd_ctrl(key, ctrl)\n\treturn ctrl\n\n\nfunc add_value(key, value, disp_text, hint=''):\n\tvar ctrl = PanelControls.StringControl.new(disp_text, value, hint)\n\tadd_ctrl(key, ctrl)\n\treturn ctrl\n\nfunc add_multiline_text(key, value, disp_text, hint=''):\n\tvar ctrl = PanelControls.MultiLineStringControl.new(disp_text, value, hint)\n\tadd_ctrl(key, ctrl)\n\treturn ctrl\n\nfunc add_boolean(key, value, disp_text, hint=''):\n\tvar ctrl = PanelControls.BooleanControl.new(disp_text, value, hint)\n\tadd_ctrl(key, ctrl)\n\treturn ctrl\n\n\nfunc add_directory(key, value, disp_text, hint=''):\n\tvar ctrl = PanelControls.DirectoryControl.new(disp_text, value, hint)\n\tadd_ctrl(key, ctrl)\n\tctrl.dialog.title = disp_text\n\treturn ctrl\n\n\nfunc add_file(key, value, disp_text, hint=''):\n\tvar ctrl = PanelControls.DirectoryControl.new(disp_text, value, hint)\n\tadd_ctrl(key, ctrl)\n\tctrl.dialog.file_mode = ctrl.dialog.FILE_MODE_OPEN_FILE\n\tctrl.dialog.title = disp_text\n\treturn ctrl\n\n\nfunc add_save_file_anywhere(key, value, disp_text, hint=''):\n\tvar ctrl = PanelControls.DirectoryControl.new(disp_text, value, hint)\n\tadd_ctrl(key, ctrl)\n\tctrl.dialog.file_mode = ctrl.dialog.FILE_MODE_SAVE_FILE\n\tctrl.dialog.access = ctrl.dialog.ACCESS_FILESYSTEM\n\tctrl.dialog.title = disp_text\n\treturn ctrl\n\n\nfunc add_color(key, value, disp_text, hint=''):\n\tvar ctrl = PanelControls.ColorControl.new(disp_text, value, hint)\n\tadd_ctrl(key, ctrl)\n\treturn ctrl\n\n\nvar _blurbs = 0\nfunc add_blurb(text):\n\tvar ctrl = RichTextLabel.new()\n\tctrl.fit_content = true\n\tctrl.bbcode_enabled = true\n\tctrl.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART\n\tctrl.text = text\n\tadd_ctrl(str(\"blurb_\", _blurbs), ctrl)\n\treturn ctrl\n\n\n# ------------------\n# Events\n# ------------------\nfunc _on_title_cell_draw(which):\n\twhich.draw_rect(Rect2(Vector2(0, 0), which.size), Color(0, 0, 0, .15))\n\n\n# ------------------\n# Public\n# ------------------\n\nfunc clear():\n\tfor key in controls:\n\t\tcontrols[key].free()\n\n\tcontrols.clear()\n\n\tfor entry in _all_titles:\n\t\tentry.free()\n\n\t_all_titles.clear()\n"
  },
  {
    "path": "demo/addons/gut/gui/option_maker.gd.uid",
    "content": "uid://bjahqsqo645sf\n"
  },
  {
    "path": "demo/addons/gut/gui/panel_controls.gd",
    "content": "# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nclass BaseGutPanelControl:\n\textends HBoxContainer\n\tvar label = Label.new()\n\tvar _lbl_unsaved = Label.new()\n\tvar _lbl_invalid = Label.new()\n\n\tvar value = null:\n\t\tget: return get_value()\n\t\tset(val): set_value(val)\n\n\tsignal changed\n\n\tfunc _init(title, val, hint=\"\"):\n\t\tsize_flags_horizontal = SIZE_EXPAND_FILL\n\t\tmouse_filter = MOUSE_FILTER_PASS\n\n\t\tlabel.size_flags_horizontal = label.SIZE_EXPAND_FILL\n\t\tlabel.mouse_filter = label.MOUSE_FILTER_STOP\n\t\tadd_child(label)\n\n\t\t_lbl_unsaved.text = '*'\n\t\t_lbl_unsaved.visible = false\n\t\tadd_child(_lbl_unsaved)\n\n\t\t_lbl_invalid.text = '!'\n\t\t_lbl_invalid.visible = false\n\t\tadd_child(_lbl_invalid)\n\n\t\tlabel.text = title\n\t\tlabel.tooltip_text = hint\n\n\n\tfunc mark_unsaved(is_it=true):\n\t\t_lbl_unsaved.visible = is_it\n\n\n\tfunc mark_invalid(is_it):\n\t\t_lbl_invalid.visible = is_it\n\n\t# -- Virtual --\n\t#\n\t# value_ctrl (all should declare the value_ctrl)\n\t#\n\tfunc set_value(value):\n\t\tpass\n\n\tfunc get_value():\n\t\tpass\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nclass NumberControl:\n\textends BaseGutPanelControl\n\n\tvar value_ctrl = SpinBox.new()\n\n\tfunc _init(title, val, v_min, v_max, hint=\"\"):\n\t\tsuper._init(title, val, hint)\n\n\t\tvalue_ctrl.value = val\n\t\tvalue_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL\n\t\tvalue_ctrl.min_value = v_min\n\t\tvalue_ctrl.max_value = v_max\n\t\tvalue_ctrl.value_changed.connect(_on_value_changed)\n\t\tvalue_ctrl.select_all_on_focus = true\n\t\tadd_child(value_ctrl)\n\n\tfunc _on_value_changed(new_value):\n\t\tchanged.emit()\n\n\tfunc get_value():\n\t\treturn value_ctrl.value\n\n\tfunc set_value(val):\n\t\tvalue_ctrl.value = val\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nclass FloatControl:\n\textends NumberControl\n\n\tfunc _init(title, val, step, v_min, v_max, hint=\"\"):\n\t\tsuper._init(title, val, v_min, v_max, hint)\n\t\tvalue_ctrl.step = step\n\t\tvalue_ctrl.value = val\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nclass StringControl:\n\textends BaseGutPanelControl\n\n\tvar value_ctrl = LineEdit.new()\n\n\tfunc _init(title, val, hint=\"\"):\n\t\tsuper._init(title, val, hint)\n\n\t\tvalue_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL\n\t\tvalue_ctrl.text = val\n\t\tvalue_ctrl.text_changed.connect(_on_text_changed)\n\t\tvalue_ctrl.select_all_on_focus = true\n\t\tadd_child(value_ctrl)\n\t\tif(title == ''):\n\t\t\tlabel.visible = false\n\n\tfunc _on_text_changed(new_value):\n\t\tchanged.emit()\n\n\tfunc get_value():\n\t\treturn value_ctrl.text\n\n\tfunc set_value(val):\n\t\tvalue_ctrl.text = val\n\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nclass MultiLineStringControl:\n\textends BaseGutPanelControl\n\n\tvar value_ctrl = TextEdit.new()\n\n\tfunc _init(title, val, hint=\"\"):\n\t\tsuper._init(title, val, hint)\n\t\tvar vbox = VBoxContainer.new()\n\t\tvbox.size_flags_horizontal = SIZE_EXPAND_FILL\n\t\tadd_child(vbox)\n\t\tlabel.reparent(vbox)\n\t\tvalue_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL\n\t\tvalue_ctrl.text = val\n\t\tvalue_ctrl.text_changed.connect(_on_text_changed)\n\t\tvalue_ctrl.scroll_fit_content_height = true\n\t\tvbox.add_child(value_ctrl)\n\n\tfunc _on_text_changed(new_value):\n\t\tchanged.emit()\n\n\tfunc get_value():\n\t\treturn value_ctrl.text\n\n\tfunc set_value(val):\n\t\tvalue_ctrl.text = val\n\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nclass BooleanControl:\n\textends BaseGutPanelControl\n\n\tvar value_ctrl = CheckBox.new()\n\n\tfunc _init(title, val, hint=\"\"):\n\t\tsuper._init(title, val, hint)\n\n\t\tvalue_ctrl.button_pressed = val\n\t\tvalue_ctrl.toggled.connect(_on_button_toggled)\n\t\tadd_child(value_ctrl)\n\n\tfunc _on_button_toggled(new_value):\n\t\tchanged.emit()\n\n\tfunc get_value():\n\t\treturn value_ctrl.button_pressed\n\n\tfunc set_value(val):\n\t\tvalue_ctrl.button_pressed = val\n\n\n# ------------------------------------------------------------------------------\n# value is \"selected\" and is gettable and settable\n# text is the text value of the selected item, it is gettable only\n# ------------------------------------------------------------------------------\nclass SelectControl:\n\textends BaseGutPanelControl\n\n\tvar value_ctrl = OptionButton.new()\n\n\tvar text = '' :\n\t\tget: return value_ctrl.get_item_text(value_ctrl.selected)\n\t\tset(val): pass\n\n\tfunc _init(title, val, choices, hint=\"\"):\n\t\tsuper._init(title, val, hint)\n\n\t\tvar select_idx = 0\n\t\tfor i in range(choices.size()):\n\t\t\tvalue_ctrl.add_item(choices[i])\n\t\t\tif(val == choices[i]):\n\t\t\t\tselect_idx = i\n\t\tvalue_ctrl.selected = select_idx\n\t\tvalue_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL\n\t\tvalue_ctrl.item_selected.connect(_on_item_selected)\n\t\tadd_child(value_ctrl)\n\n\tfunc _on_item_selected(idx):\n\t\tchanged.emit()\n\n\tfunc get_value():\n\t\treturn value_ctrl.selected\n\n\tfunc set_value(val):\n\t\tvalue_ctrl.selected = val\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nclass ColorControl:\n\textends BaseGutPanelControl\n\n\tvar value_ctrl = ColorPickerButton.new()\n\n\tfunc _init(title, val, hint=\"\"):\n\t\tsuper._init(title, val, hint)\n\t\tvalue_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL\n\t\tvalue_ctrl.color = val\n\t\tadd_child(value_ctrl)\n\n\tfunc get_value():\n\t\treturn value_ctrl.color\n\n\tfunc set_value(val):\n\t\tvalue_ctrl.color = val\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nclass DirectoryControl:\n\textends BaseGutPanelControl\n\n\tvar value_ctrl := LineEdit.new()\n\tvar dialog := FileDialog.new()\n\tvar enabled_button = CheckButton.new()\n\n\tvar _btn_dir := Button.new()\n\n\tfunc _init(title, val, hint=\"\"):\n\t\tsuper._init(title, val, hint)\n\n\t\tlabel.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN\n\n\t\t_btn_dir.text = '...'\n\t\t_btn_dir.pressed.connect(_on_dir_button_pressed)\n\n\t\tvalue_ctrl.text = val\n\t\tvalue_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL\n\t\tvalue_ctrl.select_all_on_focus = true\n\t\tvalue_ctrl.text_changed.connect(_on_value_changed)\n\n\t\tdialog.file_mode = dialog.FILE_MODE_OPEN_DIR\n\t\tdialog.unresizable = false\n\t\tdialog.dir_selected.connect(_on_selected)\n\t\tdialog.file_selected.connect(_on_selected)\n\n\t\tenabled_button.button_pressed = true\n\t\tenabled_button.visible = false\n\n\t\tadd_child(enabled_button)\n\t\tadd_child(value_ctrl)\n\t\tadd_child(_btn_dir)\n\t\tadd_child(dialog)\n\n\tfunc _update_display():\n\t\tvar is_empty = value_ctrl.text == ''\n\t\tenabled_button.button_pressed = !is_empty\n\t\tenabled_button.disabled = is_empty\n\n\n\tfunc _ready():\n\t\tif(Engine.is_editor_hint()):\n\t\t\tdialog.size = Vector2(1000, 700)\n\t\telse:\n\t\t\tdialog.size = Vector2(500, 350)\n\t\t_update_display()\n\n\tfunc _on_value_changed(new_text):\n\t\t_update_display()\n\n\tfunc _on_selected(path):\n\t\tvalue_ctrl.text = path\n\t\t_update_display()\n\n\tfunc _on_dir_button_pressed():\n\t\tdialog.current_dir = value_ctrl.text\n\t\tdialog.popup_centered()\n\n\tfunc get_value():\n\t\treturn value_ctrl.text\n\n\tfunc set_value(val):\n\t\tvalue_ctrl.text = val\n\n\n# ------------------------------------------------------------------------------\n# Features:\n# \tButtons to pick res://, user://, or anywhere on the OS.\n# ------------------------------------------------------------------------------\nclass FileDialogSuperPlus:\n\textends FileDialog\n\n\tvar show_diretory_types = true :\n\t\tset(val) :\n\t\t\tshow_diretory_types = val\n\t\t\t_update_display()\n\n\tvar show_res = true :\n\t\tset(val) :\n\t\t\tshow_res = val\n\t\t\t_update_display()\n\n\tvar show_user = true :\n\t\tset(val) :\n\t\t\tshow_user = val\n\t\t\t_update_display()\n\n\tvar show_os = true :\n\t\tset(val) :\n\t\t\tshow_os = val\n\t\t\t_update_display()\n\n\tvar _dir_type_hbox = null\n\tvar _btn_res = null\n\tvar _btn_user = null\n\tvar _btn_os = null\n\n\tfunc _ready():\n\t\t_init_controls()\n\t\t_update_display()\n\n\n\tfunc _init_controls():\n\t\t_dir_type_hbox = HBoxContainer.new()\n\n\t\t_btn_res = Button.new()\n\t\t_btn_user = Button.new()\n\t\t_btn_os = Button.new()\n\t\tvar spacer1 = CenterContainer.new()\n\t\tspacer1.size_flags_horizontal = spacer1.SIZE_EXPAND_FILL\n\t\tvar spacer2 = spacer1.duplicate()\n\n\t\t_dir_type_hbox.add_child(spacer1)\n\t\t_dir_type_hbox.add_child(_btn_res)\n\t\t_dir_type_hbox.add_child(_btn_user)\n\t\t_dir_type_hbox.add_child(_btn_os)\n\t\t_dir_type_hbox.add_child(spacer2)\n\n\t\t_btn_res.text = 'res://'\n\t\t_btn_user.text = 'user://'\n\t\t_btn_os.text = '  OS  '\n\n\t\tget_vbox().add_child(_dir_type_hbox)\n\t\tget_vbox().move_child(_dir_type_hbox, 0)\n\n\t\t_btn_res.pressed.connect(func(): access = ACCESS_RESOURCES)\n\t\t_btn_user.pressed.connect(func(): access = ACCESS_USERDATA)\n\t\t_btn_os.pressed.connect(func(): access = ACCESS_FILESYSTEM)\n\n\n\tfunc _update_display():\n\t\tif(is_inside_tree()):\n\t\t\t_dir_type_hbox.visible = show_diretory_types\n\t\t\t_btn_res.visible = show_res\n\t\t\t_btn_user.visible = show_user\n\t\t\t_btn_os.visible = show_os\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nclass SaveLoadControl:\n\textends BaseGutPanelControl\n\n\tvar btn_load = Button.new()\n\tvar btn_save = Button.new()\n\n\tvar dlg_load := FileDialogSuperPlus.new()\n\tvar dlg_save := FileDialogSuperPlus.new()\n\n\tsignal save_path_chosen(path)\n\tsignal load_path_chosen(path)\n\n\tfunc _init(title, val, hint):\n\t\tsuper._init(title, val, hint)\n\n\t\tbtn_load.text = \"Load\"\n\t\tbtn_load.custom_minimum_size.x = 100\n\t\tbtn_load.pressed.connect(_on_load_pressed)\n\t\tadd_child(btn_load)\n\n\t\tbtn_save.text = \"Save As\"\n\t\tbtn_save.custom_minimum_size.x = 100\n\t\tbtn_save.pressed.connect(_on_save_pressed)\n\t\tadd_child(btn_save)\n\n\t\tdlg_load.file_mode = dlg_load.FILE_MODE_OPEN_FILE\n\t\tdlg_load.unresizable = false\n\t\tdlg_load.dir_selected.connect(_on_load_selected)\n\t\tdlg_load.file_selected.connect(_on_load_selected)\n\t\tadd_child(dlg_load)\n\n\t\tdlg_save.file_mode = dlg_save.FILE_MODE_SAVE_FILE\n\t\tdlg_save.unresizable = false\n\t\tdlg_save.dir_selected.connect(_on_save_selected)\n\t\tdlg_save.file_selected.connect(_on_save_selected)\n\t\tadd_child(dlg_save)\n\n\n\tfunc _ready():\n\t\tif(Engine.is_editor_hint()):\n\t\t\tdlg_load.size = Vector2(1000, 700)\n\t\t\tdlg_save.size = Vector2(1000, 700)\n\t\telse:\n\t\t\tdlg_load.size = Vector2(500, 350)\n\t\t\tdlg_save.size = Vector2(500, 350)\n\n\tfunc _on_load_selected(path):\n\t\tload_path_chosen.emit(path)\n\n\tfunc _on_save_selected(path):\n\t\tsave_path_chosen.emit(path)\n\n\tfunc _on_load_pressed():\n\t\tdlg_load.popup_centered()\n\n\tfunc _on_save_pressed():\n\t\tdlg_save.popup_centered()\n\n# ------------------------------------------------------------------------------\n# This one was never used in gut_config_gui...but I put some work into it and\n# I'm a sucker for that kinda thing.  Delete this when you get tired of looking\n# at it.\n# ------------------------------------------------------------------------------\n# class Vector2Ctrl:\n# \textends VBoxContainer\n\n# \tvar value = Vector2(-1, -1) :\n# \t\tget:\n# \t\t\treturn get_value()\n# \t\tset(val):\n# \t\t\tset_value(val)\n# \tvar disabled = false :\n# \t\tget:\n# \t\t\treturn get_disabled()\n# \t\tset(val):\n# \t\t\tset_disabled(val)\n# \tvar x_spin = SpinBox.new()\n# \tvar y_spin = SpinBox.new()\n\n# \tfunc _init():\n# \t\tadd_child(_make_one('x:  ', x_spin))\n# \t\tadd_child(_make_one('y:  ', y_spin))\n\n# \tfunc _make_one(txt, spinner):\n# \t\tvar hbox = HBoxContainer.new()\n# \t\tvar lbl = Label.new()\n# \t\tlbl.text = txt\n# \t\thbox.add_child(lbl)\n# \t\thbox.add_child(spinner)\n# \t\tspinner.min_value = -1\n# \t\tspinner.max_value = 10000\n# \t\tspinner.size_flags_horizontal = spinner.SIZE_EXPAND_FILL\n# \t\treturn hbox\n\n# \tfunc set_value(v):\n# \t\tif(v != null):\n# \t\t\tx_spin.value = v[0]\n# \t\t\ty_spin.value = v[1]\n\n# \t# Returns array instead of vector2 b/c that is what is stored in\n# \t# in the dictionary and what is expected everywhere else.\n# \tfunc get_value():\n# \t\treturn [x_spin.value, y_spin.value]\n\n# \tfunc set_disabled(should):\n# \t\tget_parent().visible = !should\n# \t\tx_spin.visible = !should\n# \t\ty_spin.visible = !should\n\n# \tfunc get_disabled():\n# \t\tpass\n"
  },
  {
    "path": "demo/addons/gut/gui/panel_controls.gd.uid",
    "content": "uid://db54jy04d8w7p\n"
  },
  {
    "path": "demo/addons/gut/gui/run_from_editor.gd",
    "content": "# ------------------------------------------------------------------------------\n# This is the entry point when running tests from the editor.\n#\n# This script should conform to, or ignore, the strictest warning settings.\n# ------------------------------------------------------------------------------\nextends Node2D\n\nvar GutLoader : Object\n\nfunc _init() -> void:\n\tGutLoader = load(\"res://addons/gut/gut_loader.gd\")\n\n\n@warning_ignore(\"unsafe_method_access\")\nfunc _ready() -> void:\n\tvar runner : Node = load(\"res://addons/gut/gui/GutRunner.tscn\").instantiate()\n\tadd_child(runner)\n\trunner.run_from_editor()\n\tGutLoader.restore_ignore_addons()\n"
  },
  {
    "path": "demo/addons/gut/gui/run_from_editor.gd.uid",
    "content": "uid://bwf2iuidqfkpl\n"
  },
  {
    "path": "demo/addons/gut/gui/run_from_editor.tscn",
    "content": "[gd_scene load_steps=2 format=3 uid=\"uid://bgj3fm5d8yvjw\"]\n\n[ext_resource type=\"Script\" uid=\"uid://bwf2iuidqfkpl\" path=\"res://addons/gut/gui/run_from_editor.gd\" id=\"1_53pap\"]\n\n[node name=\"RunFromEditor\" type=\"Node2D\"]\nscript = ExtResource(\"1_53pap\")\n"
  },
  {
    "path": "demo/addons/gut/gut.gd",
    "content": "extends 'res://addons/gut/gut_to_move.gd'\nclass_name GutMain\n## The GUT brains.\n##\n## Most of this class is for internal use only.  Features that can be used are\n## have descriptions and can be accessed through the [member GutTest.gut] variable\n## in your test scripts (extends [GutTest]).\n## The wiki page for this class contains only the usable features.\n## [br][br]\n## GUT Wiki:  [url=https://gut.readthedocs.io]https://gut.readthedocs.io[/url]\n## [br]\n## @ignore-uncommented\n\n\n# ---------------------------\n# Constants\n# ---------------------------\nconst LOG_LEVEL_FAIL_ONLY = 0\nconst LOG_LEVEL_TEST_AND_FAILURES = 1\nconst LOG_LEVEL_ALL_ASSERTS = 2\nconst WAITING_MESSAGE = '/# waiting #/'\nconst PAUSE_MESSAGE = '/# Pausing.  Press continue button...#/'\nconst COMPLETED = 'completed'\n\n# ---------------------------\n# Signals\n# ---------------------------\nsignal start_pause_before_teardown\nsignal end_pause_before_teardown\n\nsignal start_run\nsignal end_run\nsignal start_script(test_script_obj)\nsignal end_script\nsignal start_test(test_name)\nsignal end_test\n\n\n# ---------------------------\n# Settings\n#\n# These are properties that are usually set before a run is started through\n# gutconfig.\n# ---------------------------\n\nvar _inner_class_name = ''\n# When set, GUT will only run Inner-Test-Classes that contain this string.\nvar inner_class_name = _inner_class_name :\n    get: return _inner_class_name\n    set(val): _inner_class_name = val\n\nvar _ignore_pause_before_teardown = false\n# For batch processing purposes, you may want to ignore any calls to\n# pause_before_teardown that you forgot to remove_at.\nvar ignore_pause_before_teardown = _ignore_pause_before_teardown :\n    get: return _ignore_pause_before_teardown\n    set(val): _ignore_pause_before_teardown = val\n\nvar _log_level = 1\n## The log detail level.  Valid values are 0 - 2.  Larger values do not matter.\nvar log_level = _log_level:\n    get: return _log_level\n    set(val): _set_log_level(val)\n\n## The amount of time that must elapse before an \"Awaiting\" message is printed.\nvar wait_log_delay = 0.5\n\n# TODO 4.0\n# This appears to not be used anymore.  Going to wait for more tests to be\n# ported before removing.\nvar _disable_strict_datatype_checks = false\nvar disable_strict_datatype_checks = false :\n    get: return _disable_strict_datatype_checks\n    set(val): _disable_strict_datatype_checks = val\n\nvar _export_path = ''\n# Path to file that GUT will create which holds a list of all test scripts so\n# that GUT can run tests when a project is exported.\nvar export_path = '' :\n    get: return _export_path\n    set(val): _export_path = val\n\nvar _include_subdirectories = false\n# Setting this to true will make GUT search all subdirectories of any directory\n# you have configured GUT to search for tests in.\nvar include_subdirectories = _include_subdirectories :\n    get: return _include_subdirectories\n    set(val): _include_subdirectories = val\n\n\nvar _double_strategy = GutUtils.DOUBLE_STRATEGY.SCRIPT_ONLY\n# TODO rework what this is and then document it here.\nvar double_strategy = _double_strategy  :\n    get: return _double_strategy\n    set(val):\n        if(GutUtils.DOUBLE_STRATEGY.values().has(val)):\n            _double_strategy = val\n            _doubler.set_strategy(double_strategy)\n        else:\n            _lgr.error(str(\"gut.gd:  invalid double_strategy \", val))\n\nvar _pre_run_script = ''\n# Path to the script that will be run before all tests are run.  This script\n# must extend GutHookScript\nvar pre_run_script = _pre_run_script :\n    get: return _pre_run_script\n    set(val): _pre_run_script = val\n\nvar _post_run_script = ''\n# Path to the script that will run after all tests have run.  The script\n# must extend GutHookScript\nvar post_run_script = _post_run_script :\n    get: return _post_run_script\n    set(val): _post_run_script = val\n\nvar _color_output = false\n# Flag to color output at the command line and in the GUT GUI.\nvar color_output = false :\n    get: return _color_output\n    set(val):\n        _color_output = val\n        _lgr.disable_formatting(!_color_output)\n\nvar _junit_xml_file = ''\n# The full path to where GUT should write a JUnit compliant XML file to which\n# contains the results of all tests run.\nvar junit_xml_file = '' :\n    get: return _junit_xml_file\n    set(val): _junit_xml_file = val\n\nvar _junit_xml_timestamp = false\n# When true and junit_xml_file is set, the file name will include a\n# timestamp so that previous files are not overwritten.\nvar junit_xml_timestamp = false :\n    get: return _junit_xml_timestamp\n    set(val): _junit_xml_timestamp = val\n\n# The minimum amout of time GUT will wait before pausing for 1 frame to allow\n# the screen to paint.  GUT checkes after each test to see if enough time has\n# passed.\nvar paint_after = .1:\n    get: return paint_after\n    set(val): paint_after = val\n\nvar _unit_test_name = ''\n# When set GUT will only run tests that contain this string.\nvar unit_test_name = _unit_test_name :\n    get: return _unit_test_name\n    set(val): _unit_test_name = val\n\nvar _parameter_handler = null\n# This is populated by test.gd each time a paramterized test is encountered\n# for the first time.\n# FOR INTERNAL USE ONLY\nvar parameter_handler = _parameter_handler :\n    get: return _parameter_handler\n    set(val):\n        _parameter_handler = val\n        _parameter_handler.set_logger(_lgr)\n\nvar _lgr = GutUtils.get_logger()\n# Local reference for the common logger.\nvar logger = _lgr :\n    get: return _lgr\n    set(val):\n        _lgr = val\n        _lgr.set_gut(self)\n\nvar error_tracker = GutUtils.get_error_tracker()\n\nvar _add_children_to = self\n# Sets the object that GUT will add test objects to as it creates them.  The\n# default is self, but can be set to other objects so that GUT is not obscured\n# by the objects added during tests.\nvar add_children_to = self :\n    get: return _add_children_to\n    set(val): _add_children_to = val\n\n\n# ------------\n# Read only\n# ------------\nvar _test_collector = GutUtils.TestCollector.new()\nfunc get_test_collector():\n    return _test_collector\n\n# var version = null :\nfunc get_version():\n    return GutUtils.version_numbers.gut_version\n\nvar _orphan_counter =  GutUtils.OrphanCounter.new()\nfunc get_orphan_counter():\n    return _orphan_counter\n\n# var _autofree = GutUtils.AutoFree.new()\nfunc get_autofree():\n    return _orphan_counter.autofree\n\nvar _stubber = GutUtils.Stubber.new()\nfunc get_stubber():\n    return _stubber\n\nvar _doubler = GutUtils.Doubler.new()\nfunc get_doubler():\n    return _doubler\n\nvar _spy = GutUtils.Spy.new()\nfunc get_spy():\n    return _spy\n\nvar _is_running = false\nfunc is_running():\n    return _is_running\n\n\n# ---------------------------\n# Private\n# ---------------------------\nvar  _should_print_versions = true # used to cut down on output in tests.\nvar _should_print_summary = true\n\nvar _file_prefix = 'test_'\nvar _inner_class_prefix = 'Test'\n\nvar _select_script = ''\nvar _last_paint_time = 0.0\nvar _strutils = GutUtils.Strutils.new()\n\n# The instance that is created from _pre_run_script.  Accessible from\n# get_pre_run_script_instance.  These are created at the start of the run\n# and then referenced at the appropriate time.  This allows us to validate the\n# scripts prior to running.\nvar _pre_run_script_instance = null\nvar _post_run_script_instance = null\n\nvar _script_name = null\n\n# The instanced scripts.  This is populated as the scripts are run.\nvar _test_script_objects = []\n\nvar _waiting = false\n\n# msecs ticks when run was started\nvar _start_time = 0.0\n\n# Collected Test instance for the current test being run.\nvar _current_test = null\nvar _pause_before_teardown = false\n\n\n# Used to cancel importing scripts if an error has occurred in the setup.  This\n# prevents tests from being run if they were exported and ensures that the\n# error displayed is seen since importing generates a lot of text.\n#\n# TODO this appears to only be checked and never set anywhere.  Verify that this\n# was not broken somewhere and remove if no longer used.\nvar _cancel_import = false\n\n# this is how long Gut will wait when there are items that must be queued free\n# when a test completes (due to calls to add_child_autoqfree)\nvar _auto_queue_free_delay = .1\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc _init(override_logger=null):\n    if(override_logger != null):\n        logger = override_logger\n    else:\n        logger = logger # force setter logic\n\n    _doubler.set_stubber(_stubber)\n    _doubler.set_spy(_spy)\n    _doubler.set_gut(self)\n\n    update_loggers()\n\n# Public for tests that set the logger.  This makes it much easier to propigate\n# test loggers.\nfunc update_loggers():\n    _doubler.set_logger(_lgr)\n    _spy.set_logger(_lgr)\n    _stubber.set_logger(_lgr)\n    _test_collector.set_logger(_lgr)\n\n\n# ------------------------------------------------------------------------------\n# Initialize controls\n# ------------------------------------------------------------------------------\nfunc _ready():\n    if(_should_print_versions):\n        _lgr.log('---  GUT  ---')\n        _lgr.info(str('using [', OS.get_user_data_dir(), '] for temporary output.'))\n\n    if(_select_script != null):\n        select_script(_select_script)\n\n    _print_versions()\n\n# ------------------------------------------------------------------------------\n# Runs right before free is called.  Can't override `free`.\n# ------------------------------------------------------------------------------\nfunc _notification(what):\n    if(what == NOTIFICATION_PREDELETE):\n        for ts in _test_script_objects:\n            if(is_instance_valid(ts)):\n                ts.free()\n\n        _test_script_objects = []\n\n\nfunc _print_versions(send_all = true):\n    if(!_should_print_versions):\n        return\n\n    var info = GutUtils.version_numbers.get_version_text()\n\n    if(send_all):\n        p(info)\n    else:\n        _lgr.get_printer('gui').send(info + \"\\n\")\n\n\n\n\n# ---------------------------\n#\n# Accessor code\n#\n# ---------------------------\n\n\n# ------------------------------------------------------------------------------\n# Set the log level.  Use one of the various LOG_LEVEL_* constants.\n# ------------------------------------------------------------------------------\nfunc _set_log_level(level):\n    _log_level = max(level, 0)\n\n    # Level 0 settings\n    _lgr.set_less_test_names(level == 0)\n    # Explicitly always enabled\n    _lgr.set_type_enabled(_lgr.types.normal, true)\n    _lgr.set_type_enabled(_lgr.types.error, true)\n    _lgr.set_type_enabled(_lgr.types.pending, true)\n\n    # Level 1 types\n    _lgr.set_type_enabled(_lgr.types.warn, level > 0)\n    _lgr.set_type_enabled(_lgr.types.deprecated, level > 0)\n\n    # Level 2 types\n    _lgr.set_type_enabled(_lgr.types.passed, level > 1)\n    _lgr.set_type_enabled(_lgr.types.info, level > 1)\n    _lgr.set_type_enabled(_lgr.types.debug, level > 1)\n\n# ---------------------------\n#\n# Events\n#\n# ---------------------------\nfunc end_teardown_pause():\n    _pause_before_teardown = false\n    _waiting = false\n    end_pause_before_teardown.emit()\n\n# ---------------------------\n#\n# Private\n#\n# ---------------------------\nfunc _log_test_children_warning(test_script):\n    if(!_lgr.is_type_enabled(_lgr.types.orphan)):\n        return\n\n    var kids = test_script.get_children()\n    if(kids.size() > 1):\n        var msg = ''\n        if(_log_level == 2):\n            msg = \"Test script still has children when all tests finisehd.\\n\"\n            for i in range(kids.size()):\n                msg += str(\"  \", _strutils.type2str(kids[i]), \"\\n\")\n            msg += \"You can use autofree, autoqfree, add_child_autofree, or add_child_autoqfree to automatically free objects.\"\n        else:\n            msg = str(\"Test script has \", kids.size(), \" unfreed children.  Increase log level for more details.\")\n\n        _lgr.warn(msg)\n\n\nfunc _log_end_run():\n    var summary = GutUtils.Summary.new(self)\n    if(_should_print_summary):\n        _orphan_counter.record_orphans(\"end_run\")\n        if(_lgr.is_type_enabled(\"orphan\") and _orphan_counter.get_count() > 0):\n            _lgr.log(\"\\n\\n\\n\")\n            _lgr.orphan(\"==============================================\")\n            _lgr.orphan(str('= ', _orphan_counter.get_count(), ' Orphans'))\n            _lgr.orphan(\"==============================================\")\n            _orphan_counter.log_all()\n            _lgr.log(\"\\n\")\n        else:\n            _lgr.log(\"\\n\\n\\n\")\n\n        summary.log_end_run()\n\n\nfunc _validate_hook_script(path):\n    var result = {\n        valid = true,\n        instance = null\n    }\n\n    # empty path is valid but will have a null instance\n    if(path == ''):\n        return result\n\n    if(FileAccess.file_exists(path)):\n        var inst = load(path).new()\n        if(inst and inst is GutHookScript):\n            result.instance = inst\n            result.valid = true\n        else:\n            result.valid = false\n            _lgr.error('The hook script [' + path + '] does not extend GutHookScript')\n    else:\n        result.valid = false\n        _lgr.error('The hook script [' + path + '] does not exist.')\n\n    return result\n\n\n# ------------------------------------------------------------------------------\n# Runs a hook script.  Script must exist, and must extend\n# GutHookScript or addons/gut/hook_script.gd\n# ------------------------------------------------------------------------------\nfunc _run_hook_script(inst):\n    if(inst != null):\n        inst.gut = self\n        await inst.run()\n    return inst\n\n\n# ------------------------------------------------------------------------------\n# Initialize variables for each run of a single test script.\n# ------------------------------------------------------------------------------\nfunc _init_run():\n    var valid = true\n    _test_collector.set_test_class_prefix(_inner_class_prefix)\n    _test_script_objects = []\n    _current_test = null\n    _is_running = true\n\n    var pre_hook_result = _validate_hook_script(_pre_run_script)\n    _pre_run_script_instance = pre_hook_result.instance\n    var post_hook_result = _validate_hook_script(_post_run_script)\n    _post_run_script_instance  = post_hook_result.instance\n\n    valid = pre_hook_result.valid and  post_hook_result.valid\n\n    return valid\n\n\n# ------------------------------------------------------------------------------\n# Print out run information and close out the run.\n# ------------------------------------------------------------------------------\nfunc _end_run():\n    await _run_hook_script(get_post_run_script_instance())\n\n    _orphan_counter.record_orphans(\"end_run\")\n    _orphan_counter.orphanage.clean()\n    _log_end_run()\n    _is_running = false\n\n    _export_results()\n    end_run.emit()\n\n\n# ------------------------------------------------------------------------------\n# Add additional export types here.\n# ------------------------------------------------------------------------------\nfunc _export_results():\n    if(_junit_xml_file != ''):\n        _export_junit_xml()\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc _export_junit_xml():\n    var exporter = GutUtils.JunitXmlExport.new()\n    var output_file = _junit_xml_file\n\n    if(_junit_xml_timestamp):\n        var ext = \".\" + output_file.get_extension()\n        output_file = output_file.replace(ext, str(\"_\", Time.get_unix_time_from_system(), ext))\n\n    var f_result = exporter.write_file(self, output_file)\n    if(f_result == OK):\n        p(str(\"Results saved to \", output_file))\n\n\n# ------------------------------------------------------------------------------\n# Print out the heading for a new script\n# ------------------------------------------------------------------------------\nfunc _print_script_heading(coll_script):\n    if(_does_class_name_match(_inner_class_name, coll_script.inner_class_name)):\n        _lgr.log(str(\"\\n\\n\", coll_script.get_full_name()), _lgr.fmts.underline)\n\n\n# ------------------------------------------------------------------------------\n# Yes if the class name is null or the script's class name includes class_name\n# ------------------------------------------------------------------------------\nfunc _does_class_name_match(the_class_name, script_class_name):\n    return (the_class_name == null or the_class_name == '') or \\\n        (script_class_name != null and str(script_class_name).findn(the_class_name) != -1)\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc _create_script_instance(collected_script):\n    var test_script = collected_script.get_new()\n\n    test_script.gut = self\n    test_script.set_logger(_lgr)\n    _add_children_to.add_child(test_script)\n    _test_script_objects.append(test_script)\n    test_script.wait_log_delay = wait_log_delay\n\n    if(!test_script._was_ready_called):\n        test_script._do_ready_stuff()\n        _lgr.warn(str(\"!!! YOU HAVE UPSET YOUR GUT !!!\\n\",\n            \"You have overridden _ready in [\", collected_script.get_filename_and_inner(), \"] \",\n            \"but it does not call super._ready().  New additions (or maybe old \",\n            \"by the time you see this) require that super._ready() is called.\",\n            \"\\n\\n\",\n            \"GUT is working around this infraction, but may not be able to in \",\n            \"the future.  GUT also reserves the right to decide it does not want \",\n            \"to work around it in the future.  \",\n            \"You should probably use before_all instead of _ready.  I can think \",\n            \"of a few reasons why you would want to use _ready but I won't list \",\n            \"them here because I think they are bad ideas.  I know they are bad \",\n            \"ideas because I did them.  Hence the warning.  This message is \",\n            \"intentially long so that it bothers you and you change your ways.\\n\\n\",\n            \"Thank you for using GUT.\"))\n    return test_script\n\n\n# ------------------------------------------------------------------------------\n# returns self so it can be integrated into the yield call.\n# ------------------------------------------------------------------------------\nfunc _wait_for_continue_button():\n    p(PAUSE_MESSAGE, 0)\n    _waiting = true\n    return self\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc _get_indexes_matching_script_name(script_name):\n    var indexes = [] # empty runs all\n    for i in range(_test_collector.scripts.size()):\n        if(_test_collector.scripts[i].get_filename().find(script_name) != -1):\n            indexes.append(i)\n    return indexes\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc _get_indexes_matching_path(path):\n    var indexes = []\n    for i in range(_test_collector.scripts.size()):\n        if(_test_collector.scripts[i].path == path):\n            indexes.append(i)\n    return indexes\n\n\n# ------------------------------------------------------------------------------\n# Execute all calls of a parameterized test.\n# ------------------------------------------------------------------------------\nfunc _run_parameterized_test(test_script, test_name):\n    await _run_test(test_script, test_name, 0)\n\n    if(_current_test.assert_count == 0 and !_current_test.pending):\n        _lgr.risky('Test did not assert')\n\n    if(_parameter_handler == null):\n        _lgr.error(str('Parameterized test ', _current_test.name, ' did not call use_parameters for the default value of the parameter.'))\n        _fail(str('Parameterized test ', _current_test.name, ' did not call use_parameters for the default value of the parameter.'))\n    else:\n        var index = 1\n        while(!_parameter_handler.is_done()):\n            var cur_assert_count = _current_test.assert_count\n            await _run_test(test_script, test_name, index)\n            if(_current_test.assert_count == cur_assert_count and !_current_test.pending):\n                _lgr.risky('Test did not assert')\n            index += 1\n\n    _parameter_handler = null\n\n\n# ------------------------------------------------------------------------------\n# Runs a single test given a test.gd instance and the name of the test to run.\n# ------------------------------------------------------------------------------\nfunc _run_test(script_inst, test_name, param_index = -1):\n    _lgr.log_test_name()\n    _lgr.set_indent_level(1)\n\n    await script_inst.before_each()\n\n    start_test.emit(test_name)\n    var test_id = str(script_inst.collected_script.get_filename_and_inner(), ':', test_name)\n    if(param_index != -1):\n        test_id += str('[', param_index, ']')\n    error_tracker.start_test(test_id)\n\n    await script_inst.call(test_name)\n\n    if(error_tracker.should_test_fail_from_errors(test_id)):\n        script_inst._fail(str(\"Unexpected Errors:\\n\", error_tracker.get_fail_text_for_errors(test_id)))\n\n    error_tracker.end_test()\n    # if the test called pause_before_teardown then await until\n    # the continue button is pressed.\n    if(_pause_before_teardown and !_ignore_pause_before_teardown):\n        start_pause_before_teardown.emit()\n        await _wait_for_continue_button().end_pause_before_teardown\n\n    script_inst.clear_signal_watcher()\n\n    await script_inst.after_each()\n\n    # Free up everything in the _autofree.  Yield for a bit if we\n    # have anything with a queue_free so that they have time to\n    # free and are not found by the orphan counter.\n    var aqf_count = _orphan_counter.autofree.get_queue_free_count()\n    _orphan_counter.autofree.free_all()\n    if(aqf_count > 0):\n        await get_tree().create_timer(_auto_queue_free_delay).timeout\n\n    _orphan_counter.end_test(\n        script_inst.collected_script.get_filename_and_inner(), test_name,\n        _log_level > 0)\n\n    _doubler.get_ignored_methods().clear()\n\n\nfunc get_current_test_orphans():\n    var sname = get_current_test_object().collected_script.get_ref().get_filename_and_inner()\n    var tname = get_current_test_object().name\n    _orphan_counter.record_orphans(sname, tname)\n    return _orphan_counter.get_orphan_ids(sname, tname)\n\n\n# ------------------------------------------------------------------------------\n# Calls before_all on the passed in test script and takes care of settings so all\n# logger output appears indented and with a proper heading\n#\n# Calls both pre-all-tests methods until prerun_setup is removed\n# ------------------------------------------------------------------------------\nfunc _call_before_all(test_script, collected_script):\n    var before_all_test_obj = GutUtils.CollectedTest.new()\n    before_all_test_obj.has_printed_name = false\n    before_all_test_obj.name = 'before_all'\n\n    collected_script.setup_teardown_tests.append(before_all_test_obj)\n    _current_test = before_all_test_obj\n\n    _lgr.inc_indent()\n    await test_script.before_all()\n    # before all does not need to assert anything so only mark it as run if\n    # some assert was done.\n    before_all_test_obj.was_run = before_all_test_obj.did_something()\n\n    _lgr.dec_indent()\n\n    _current_test = null\n\n\n# ------------------------------------------------------------------------------\n# Calls after_all on the passed in test script and takes care of settings so all\n# logger output appears indented and with a proper heading\n#\n# Calls both post-all-tests methods until postrun_teardown is removed.\n# ------------------------------------------------------------------------------\nfunc _call_after_all(test_script, collected_script):\n    var after_all_test_obj = GutUtils.CollectedTest.new()\n    after_all_test_obj.has_printed_name = false\n    after_all_test_obj.name = 'after_all'\n\n    collected_script.setup_teardown_tests.append(after_all_test_obj)\n    _current_test = after_all_test_obj\n\n    _lgr.inc_indent()\n    await test_script.after_all()\n    # after all does not need to assert anything so only mark it as run if\n    # some assert was done.\n    after_all_test_obj.was_run = after_all_test_obj.did_something()\n    _lgr.dec_indent()\n\n    _current_test = null\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc _should_skip_script(test_script, collected_script):\n    var skip_message = 'not skipped'\n    var skip_value = test_script.get('skip_script')\n    var should_skip = false\n\n    if(skip_value == null):\n        skip_value = await test_script.should_skip_script()\n    else:\n        _lgr.deprecated('Using the skip_script var has been deprecated.  Implement the new should_skip_script() method in your test instead.')\n\n    if(skip_value != null):\n        if(typeof(skip_value) == TYPE_BOOL):\n            should_skip = skip_value\n            if(skip_value):\n                skip_message = 'script marked to skip'\n        elif(typeof(skip_value) == TYPE_STRING):\n            should_skip = true\n            skip_message = skip_value\n\n    if(should_skip):\n        var msg = str('- [Script skipped]:  ', skip_message)\n        _lgr.inc_indent()\n        _lgr.log(msg, _lgr.fmts.yellow)\n        _lgr.dec_indent()\n        collected_script.skip_reason = skip_message\n        collected_script.was_skipped = true\n\n    return should_skip\n\n\n# ------------------------------------------------------------------------------\n# Run all tests in a script.  This is the core logic for running tests.\n# ------------------------------------------------------------------------------\nfunc _test_the_scripts(indexes=[]):\n\n    _print_versions(false)\n    var is_valid = _init_run()\n    if(!is_valid):\n        _lgr.error('Something went wrong and the run was aborted.')\n        return\n\n    await _run_hook_script(get_pre_run_script_instance())\n    if(_pre_run_script_instance!= null and _pre_run_script_instance.should_abort()):\n        _lgr.error('pre-run abort')\n        end_run.emit()\n        return\n\n    start_run.emit()\n    _start_time = Time.get_ticks_msec()\n    _last_paint_time = _start_time\n\n    var indexes_to_run = []\n    if(indexes.size()==0):\n        for i in range(_test_collector.scripts.size()):\n            indexes_to_run.append(i)\n    else:\n        indexes_to_run = indexes\n\n\n    # loop through scripts\n    for test_indexes in range(indexes_to_run.size()):\n        var coll_script = _test_collector.scripts[indexes_to_run[test_indexes]]\n\n        if(coll_script.tests.size() > 0):\n            _lgr.set_indent_level(0)\n            _print_script_heading(coll_script)\n\n        if(!coll_script.is_loaded):\n            break\n\n        start_script.emit(coll_script)\n\n        var test_script = _create_script_instance(coll_script)\n        _doubler.set_strategy(_double_strategy)\n\n        # ----\n        # SHORTCIRCUIT\n        # skip_script logic\n        if(await _should_skip_script(test_script, coll_script)):\n            _orphan_counter.record_orphans(coll_script.get_full_name())\n            continue\n        # ----\n\n        # !!!\n        # Hack so there isn't another indent to this monster of a method.  if\n        # inner class is set and we do not have a match then empty the tests\n        # for the current test.\n        # !!!\n        if(!_does_class_name_match(_inner_class_name, coll_script.inner_class_name)):\n            coll_script.tests = []\n        else:\n            coll_script.was_run = true\n            await _call_before_all(test_script, coll_script)\n\n        _orphan_counter.record_orphans(coll_script.get_full_name())\n        # Each test in the script\n        for i in range(coll_script.tests.size()):\n            _stubber.clear()\n            _spy.clear()\n            _current_test = coll_script.tests[i]\n\n            if((_unit_test_name != '' and _current_test.name.find(_unit_test_name) > -1) or\n                (_unit_test_name == '')):\n\n                var ticks_before := Time.get_ticks_usec()\n\n                if(_current_test.arg_count > 1):\n                    _lgr.error(str('Parameterized test ', _current_test.name,\n                        ' has too many parameters:  ', _current_test.arg_count, '.'))\n                elif(_current_test.arg_count == 1):\n                    _current_test.was_run = true\n                    await _run_parameterized_test(test_script, _current_test.name)\n                else:\n                    _current_test.was_run = true\n                    await _run_test(test_script, _current_test.name)\n\n                if(!_current_test.did_something()):\n                    _lgr.risky(str(_current_test.name, ' did not assert'))\n\n                _current_test.has_printed_name = false\n\n                _current_test.time_taken = (Time.get_ticks_usec() - ticks_before) / 1000000.0\n\n                end_test.emit()\n\n                # After each test, check to see if we shoudl wait a frame to\n                # paint based on how much time has elapsed since we last 'painted'\n                if(paint_after > 0.0):\n                    var now = Time.get_ticks_msec()\n                    var time_since = (now - _last_paint_time) / 1000.0\n                    if(time_since > paint_after):\n                        _last_paint_time = now\n                        await get_tree().process_frame\n\n        _current_test = null\n        _lgr.dec_indent()\n\n        if(_does_class_name_match(_inner_class_name, coll_script.inner_class_name)):\n            await _call_after_all(test_script, coll_script)\n\n        _orphan_counter.end_script(\n            coll_script.get_filename_and_inner(),\n            _log_level > 0)\n\n        _log_test_children_warning(test_script)\n        # This might end up being very resource intensive if the scripts\n        # don't clean up after themselves.  Might have to consolidate output\n        # into some other structure and kill the script objects with\n        # test_script.free() instead of remove_at child.\n        _add_children_to.remove_child(test_script)\n\n        _lgr.set_indent_level(0)\n        if(test_script.get_assert_count() > 0):\n            var script_sum = str(coll_script.get_passing_test_count(), '/', coll_script.get_ran_test_count(), ' passed.')\n            _lgr.log(script_sum, _lgr.fmts.bold)\n\n        test_script.queue_free()\n        end_script.emit()\n        # END TEST SCRIPT LOOP\n\n    _lgr.set_indent_level(0)\n    # Give anything that is queued to be freed time to be freed before we count\n    # the orphans.  Without this, the last test's awaiter won't be freed\n    # yet, which messes with the orphans total.  There could also be objects\n    # the user has queued to be freed as well.\n    # Bump number from .1 to .5 when inner classes that were not run were still\n    # appearing as orphans.  Maybe this could loop through the orpahns looking\n    # for entries that were not freed but are queued to be freed and wait unitl\n    # they are all gone.  \".5\" is a lot easier.\n    await get_tree().create_timer(.5).timeout\n    _end_run()\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc _pass(text=''):\n    if(_current_test):\n        _current_test.add_pass(text)\n\n\n# ------------------------------------------------------------------------------\n# Returns an empty string or \"(call #x) \" if the current test being run has\n# parameters.  The\n# ------------------------------------------------------------------------------\nfunc get_call_count_text():\n    var to_return = ''\n    if(_parameter_handler != null):\n        # This uses get_call_count -1 because test.gd's use_parameters method\n        # should have been called before we get to any calls for this method\n        # just due to how use_parameters works.  There isn't a way to know\n        # whether we are before or after that call.\n        to_return = str('params[', _parameter_handler.get_call_count() -1, '] ')\n    return to_return\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc _fail(text=''):\n    if(_current_test != null):\n        var line_number = _extract_line_number(_current_test)\n        var line_text = '  at line ' + str(line_number)\n        p(line_text, LOG_LEVEL_FAIL_ONLY)\n        # format for summary\n        line_text =  \"\\n    \" + line_text\n        var call_count_text = get_call_count_text()\n        _current_test.line_number = line_number\n        _current_test.add_fail(call_count_text + text + line_text)\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc _pending(text=''):\n    if(_current_test):\n        _current_test.add_pending(text)\n\n\n# ------------------------------------------------------------------------------\n# Extracts the line number from curren stacktrace by matching the test case name\n# ------------------------------------------------------------------------------\nfunc _extract_line_number(current_test):\n    var line_number = -1\n    # if stack trace available than extraxt the test case line number\n    var stackTrace = get_stack()\n    if(stackTrace!=null):\n        for index in stackTrace.size():\n            var line = stackTrace[index]\n            var function = line.get(\"function\")\n            if function == current_test.name:\n                line_number = line.get(\"line\")\n    return line_number\n\n\n# ------------------------------------------------------------------------------\n# Gets all the files in a directory and all subdirectories if include_subdirectories\n# is true.  The files returned are all sorted by name.\n# ------------------------------------------------------------------------------\nfunc _get_files(path, prefix, suffix):\n    var files = []\n    var directories = []\n    # ignore addons/gut per issue 294\n    if(path == 'res://addons/gut'):\n        return [];\n\n    var d = DirAccess.open(path)\n    d.include_hidden = false\n    d.include_navigational = false\n\n    # Traversing a directory is kinda odd.  You have to start the process of\n    # listing the contents of a directory with list_dir_begin then use get_next\n    # until it returns an empty string.  Then I guess you should end it.\n    d.list_dir_begin()\n    var fs_item = d.get_next()\n    var full_path = ''\n    while(fs_item != ''):\n        full_path = path.path_join(fs_item)\n\n        # MUST use FileAccess since d.file_exists returns false for exported\n        # projects\n        if(FileAccess.file_exists(full_path)):\n            if(fs_item.begins_with(prefix) and fs_item.ends_with(suffix)):\n                files.append(full_path)\n        # MUST use DirAccess, d.dir_exists is false for exported projects.\n        elif(include_subdirectories and DirAccess.dir_exists_absolute(full_path)):\n            directories.append(full_path)\n\n        fs_item = d.get_next()\n    d.list_dir_end()\n\n    for dir in range(directories.size()):\n        var dir_files = _get_files(directories[dir], prefix, suffix)\n        for i in range(dir_files.size()):\n            files.append(dir_files[i])\n\n    files.sort()\n    return files\n\n\n# ---------------------------\n#\n# public\n#\n# ---------------------------\n\nfunc get_elapsed_time() -> float:\n    var to_return = 0.0\n    if(_start_time != 0.0):\n        to_return = Time.get_ticks_msec() - _start_time\n    to_return = to_return / 1000.0\n\n    return to_return\n\n# ------------------------------------------------------------------------------\n# Conditionally prints the text to the console/results variable based on the\n# current log level and what level is passed in.  Whenever currently in a test,\n# the text will be indented under the test.  It can be further indented if\n# desired.\n#\n# The first time output is generated when in a test, the test name will be\n# printed.\n# ------------------------------------------------------------------------------\nfunc p(text, level=0):\n    var str_text = str(text)\n\n    if(level <= GutUtils.nvl(_log_level, 0)):\n        _lgr.log(str_text)\n\n# ---------------------------\n#\n# RUN TESTS/ADD SCRIPTS\n#\n# ---------------------------\n\n# ------------------------------------------------------------------------------\n# Runs all the scripts that were added using add_script\n# ------------------------------------------------------------------------------\nfunc test_scripts(_run_rest=false):\n    if(_script_name != null and _script_name != ''):\n        var indexes = _get_indexes_matching_script_name(_script_name)\n        if(indexes == []):\n            _lgr.error(str(\n                \"Could not find script matching '\", _script_name, \"'.\\n\",\n                \"Check your directory settings and Script Prefix/Suffix settings.\"))\n            end_run.emit()\n        else:\n            _test_the_scripts(indexes)\n    else:\n        _test_the_scripts([])\n\n# alias\nfunc run_tests(run_rest=false):\n    test_scripts(run_rest)\n\n\n# ------------------------------------------------------------------------------\n# Runs a single script passed in.\n# ------------------------------------------------------------------------------\n# func run_test_script(script):\n# \t_test_collector.set_test_class_prefix(_inner_class_prefix)\n# \t_test_collector.clear()\n# \t_test_collector.add_script(script)\n# \t_test_the_scripts()\n\n\n# ------------------------------------------------------------------------------\n# Adds a script to be run when test_scripts called.\n# ------------------------------------------------------------------------------\nfunc add_script(script):\n    # if(!Engine.is_editor_hint()):\n    _test_collector.set_test_class_prefix(_inner_class_prefix)\n    _test_collector.add_script(script)\n\n\n# ------------------------------------------------------------------------------\n# Add all scripts in the specified directory that start with the prefix and end\n# with the suffix.  Does not look in sub directories.  Can be called multiple\n# times.\n# ------------------------------------------------------------------------------\nfunc add_directory(path, prefix=_file_prefix, suffix=\".gd\"):\n    # check for '' b/c the calls to addin the exported directories 1-6 will pass\n    # '' if the field has not been populated.  This will cause res:// to be\n    # processed which will include all files if include_subdirectories is true.\n    if(path == '' or path == null):\n        return\n\n    var dir = DirAccess.open(path)\n    if(dir == null):\n        _lgr.error(str('The path [', path, '] does not exist.'))\n    else:\n        var files = _get_files(path, prefix, suffix)\n        for i in range(files.size()):\n            if(_script_name == null or _script_name == '' or \\\n                    (_script_name != null and files[i].findn(_script_name) != -1)):\n                add_script(files[i])\n\n\n# ------------------------------------------------------------------------------\n# This will try to find a script in the list of scripts to test that contains\n# the specified script name.  It does not have to be a full match.  It will\n# select the first matching occurrence so that this script will run when run_tests\n# is called.  Works the same as the select_this_one option of add_script.\n#\n# returns whether it found a match or not\n# ------------------------------------------------------------------------------\nfunc select_script(script_name):\n    _script_name = script_name\n    _select_script = script_name\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc export_tests(path=_export_path):\n    if(path == null):\n        _lgr.error('You must pass a path or set the export_path before calling export_tests')\n    else:\n        var result = _test_collector.export_tests(path)\n        if(result):\n            _lgr.info(_test_collector.to_s())\n            _lgr.info(\"Exported to \" + path)\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc import_tests(path=_export_path):\n    if(!FileAccess.file_exists(path)):\n        _lgr.error(str('Cannot import tests:  the path [', path, '] does not exist.'))\n    else:\n        _test_collector.clear()\n        var result = _test_collector.import_tests(path)\n        if(result):\n            _lgr.info(\"\\n\" + _test_collector.to_s())\n            _lgr.info(\"Imported from \" + path)\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc import_tests_if_none_found():\n    if(!_cancel_import and _test_collector.scripts.size() == 0):\n        import_tests()\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc export_if_tests_found():\n    if(_test_collector.scripts.size() > 0):\n        export_tests()\n\n# ---------------------------\n#\n# MISC\n#\n# ---------------------------\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc maximize():\n    _lgr.deprecated('gut.maximize')\n\n\n# ------------------------------------------------------------------------------\n# Clears the text of the text box.  This resets all counters.\n# ------------------------------------------------------------------------------\nfunc clear_text():\n    _lgr.deprecated('gut.clear_text')\n\n\n# ------------------------------------------------------------------------------\n# Get the number of tests that were ran\n# ------------------------------------------------------------------------------\nfunc get_test_count():\n    return _test_collector.get_ran_test_count()\n\n# ------------------------------------------------------------------------------\n## Get the number of assertions that were made\nfunc get_assert_count():\n    return _test_collector.get_assert_count()\n\n# ------------------------------------------------------------------------------\n## Get the number of assertions that passed\nfunc get_pass_count():\n    return _test_collector.get_pass_count()\n\n# ------------------------------------------------------------------------------\n## Get the number of assertions that failed\nfunc get_fail_count():\n    return _test_collector.get_fail_count()\n\n# ------------------------------------------------------------------------------\n## Get the number of tests flagged as pending\nfunc get_pending_count():\n    return _test_collector.get_pending_count()\n\n\n# ------------------------------------------------------------------------------\n# Call this method to make the test pause before teardown so that you can inspect\n# anything that you have rendered to the screen.\n# ------------------------------------------------------------------------------\nfunc pause_before_teardown():\n    _pause_before_teardown = true;\n\n\n# ------------------------------------------------------------------------------\n# Returns the script object instance that is currently being run.\n# ------------------------------------------------------------------------------\nfunc get_current_script_object():\n    var to_return = null\n    if(_test_script_objects.size() > 0):\n        to_return = _test_script_objects[-1]\n    return to_return\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc get_current_test_object():\n    return _current_test\n\n\n## Returns a summary.gd object that contains all the information about\n## the run results.\nfunc get_summary():\n    return GutUtils.Summary.new(self)\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc get_pre_run_script_instance():\n    return _pre_run_script_instance\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc get_post_run_script_instance():\n    return _post_run_script_instance\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc show_orphans(should):\n    _lgr.set_type_enabled(_lgr.types.orphan, should)\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc get_logger():\n    return _lgr\n\n\n# ------------------------------------------------------------------------------\n## Returns the number of test scripts.  Inner Test classes each count as a\n## script.\nfunc get_test_script_count():\n    return _test_script_objects.size()\n\n\n\n\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n"
  },
  {
    "path": "demo/addons/gut/gut.gd.uid",
    "content": "uid://duvvxvj18c04d\n"
  },
  {
    "path": "demo/addons/gut/gut_cmdln.gd",
    "content": "# ------------------------------------------------------------------------------\n# Description\n# -----------\n# Entry point for the command line interface.  The actual logic for GUT's CLI\n# is in addons/gut/cli/gut_cli.gd.\n#\n# This script should conform to, or ignore, the strictest warning settings.\n# ------------------------------------------------------------------------------\nextends SceneTree\n\nvar VersionConversion = load(\"res://addons/gut/version_conversion.gd\")\n\n@warning_ignore(\"unsafe_method_access\")\n@warning_ignore(\"inferred_declaration\")\nfunc _init() -> void:\n\tif(VersionConversion.error_if_not_all_classes_imported()):\n\t\tquit(0)\n\t\treturn\n\n\tvar max_iter := 20\n\tvar iter := 0\n\n\tvar Loader : Object = load(\"res://addons/gut/gut_loader.gd\")\n\n\t# Not seen this wait more than 1.\n\twhile(Engine.get_main_loop() == null and iter < max_iter):\n\t\tawait create_timer(.01).timeout\n\t\titer += 1\n\n\tif(Engine.get_main_loop() == null):\n\t\tpush_error('Main loop did not start in time.')\n\t\tquit(0)\n\t\treturn\n\n\tvar cli : Node = load('res://addons/gut/cli/gut_cli.gd').new()\n\tget_root().add_child(cli)\n\n\tLoader.restore_ignore_addons()\n\tcli.main()\n\n\n\n\n# ##############################################################################\n#(G)odot (U)nit (T)est class\n#\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n"
  },
  {
    "path": "demo/addons/gut/gut_cmdln.gd.uid",
    "content": "uid://bxw30gwbwnh55\n"
  },
  {
    "path": "demo/addons/gut/gut_config.gd",
    "content": "# ##############################################################################\n#\n# This holds all the configuratoin values for GUT.  It can load and save values\n# to a json file.  It is also responsible for applying these settings to GUT.\n#\n# ##############################################################################\nconst FAIL_ERROR_TYPE_ENGINE = &'engine'\nconst FAIL_ERROR_TYPE_PUSH_ERROR = &'push_error'\nconst FAIL_ERROR_TYPE_GUT = &'gut'\n\n\n\nvar valid_fonts = GutUtils.gut_fonts.get_font_names()\nvar _deprecated_values = {\n\t\"errors_do_not_cause_failure\": \"Use failure_error_types instead.\"\n}\n\nvar default_options = {\n\tbackground_color = Color(.15, .15, .15, 1).to_html(),\n\tconfig_file = 'res://.gutconfig.json',\n\t# used by editor to handle enabled/disabled dirs.  All dirs configured go\n\t# here and only the enabled dirs go into dirs\n\tconfigured_dirs = [],\n\tdirs = [],\n\tdisable_colors = false,\n\t# double strategy can be the name of the enum value, the enum value or\n\t# lowercase name with spaces:  0/SCRIPT_ONLY/script only\n\t# The GUI gut config expects the value to be the enum value and not a string\n\t# when saved.\n\tdouble_strategy = 'SCRIPT_ONLY',\n\tfont_color = Color(.8, .8, .8, 1).to_html(),\n\tfont_name = GutUtils.gut_fonts.DEFAULT_CUSTOM_FONT_NAME,\n\tfont_size = 16,\n\thide_orphans = false,\n\tignore_pause = false,\n\tinclude_subdirs = false,\n\tinner_class = '',\n\tjunit_xml_file = '',\n\tjunit_xml_timestamp = false,\n\tlog_level = 1,\n\topacity = 100,\n\tpaint_after = .1,\n\tpost_run_script = '',\n\tpre_run_script = '',\n\tprefix = 'test_',\n\tselected = '',\n\tshould_exit_on_success = false,\n\tshould_exit = false,\n\tshould_maximize = false,\n\tcompact_mode = false,\n\tshow_help = false,\n\tsuffix = '.gd',\n\ttests = [],\n\tunit_test_name = '',\n\n\tno_error_tracking = false,\n\tfailure_error_types = [\"engine\", \"gut\", \"push_error\"],\n\twait_log_delay = .5,\n\n\tgut_on_top = true,\n}\n\n\nvar options = default_options.duplicate()\nvar logger = GutUtils.get_logger()\n\nfunc _null_copy(h):\n\tvar new_hash = {}\n\tfor key in h:\n\t\tnew_hash[key] = null\n\treturn new_hash\n\n\nfunc _load_options_from_config_file(file_path, into):\n\tif(!FileAccess.file_exists(file_path)):\n\t\t# Default files are ok to be missing.  Maybe this is too deep a place\n\t\t# to implement this, but here it is.\n\t\tif(file_path != 'res://.gutconfig.json' and file_path != GutUtils.EditorGlobals.editor_run_gut_config_path):\n\t\t\tlogger.error(str('Config File \"', file_path, '\" does not exist.'))\n\t\t\treturn -1\n\t\telse:\n\t\t\treturn 1\n\n\tvar f = FileAccess.open(file_path, FileAccess.READ)\n\tif(f == null):\n\t\tvar result = FileAccess.get_open_error()\n\t\tlogger.error(str(\"Could not load data \", file_path, ' ', result))\n\t\treturn result\n\n\tvar json = f.get_as_text()\n\tf = null # close file\n\n\tvar test_json_conv = JSON.new()\n\ttest_json_conv.parse(json)\n\tvar results = test_json_conv.get_data()\n\t# SHORTCIRCUIT\n\tif(results == null):\n\t\tlogger.error(str(\"Could not parse file:  \", file_path))\n\t\treturn -1\n\n\t# Get all the options out of the config file using the option name.  The\n\t# options hash is now the default source of truth for the name of an option.\n\t_load_dict_into(results, into)\n\n\treturn 1\n\n\nfunc _load_dict_into(source, dest):\n\tfor key in dest:\n\t\tif(source.has(key)):\n\t\t\tif(source[key] != null):\n\t\t\t\tif(typeof(source[key]) == TYPE_DICTIONARY):\n\t\t\t\t\t_load_dict_into(source[key], dest[key])\n\t\t\t\telse:\n\t\t\t\t\tdest[key] = source[key]\n\n\n# Apply all the options specified to tester.  This is where the rubber meets\n# the road.\nfunc _apply_options(opts, gut):\n\tfor entry in _deprecated_values.keys():\n\t\tif(opts.has(entry)):\n\t\t\t# Use gut.logger instead of our own for testing purposes.\n\t\t\tlogger.deprecated(str('Config value \"', entry, '\" is deprecated.  ', _deprecated_values[entry]))\n\n\tgut.include_subdirectories = opts.include_subdirs\n\n\tif(opts.inner_class != ''):\n\t\tgut.inner_class_name = opts.inner_class\n\tgut.log_level = opts.log_level\n\tgut.ignore_pause_before_teardown = opts.ignore_pause\n\n\tgut.select_script(opts.selected)\n\n\tfor i in range(opts.dirs.size()):\n\t\tgut.add_directory(opts.dirs[i], opts.prefix, opts.suffix)\n\n\tfor i in range(opts.tests.size()):\n\t\tgut.add_script(opts.tests[i])\n\n\t# Sometimes it is the index, sometimes it's a string.  This sets it regardless\n\tgut.double_strategy = GutUtils.get_enum_value(\n\t\topts.double_strategy, GutUtils.DOUBLE_STRATEGY,\n\t\tGutUtils.DOUBLE_STRATEGY.SCRIPT_ONLY)\n\n\tgut.unit_test_name = opts.unit_test_name\n\tgut.pre_run_script = opts.pre_run_script\n\tgut.post_run_script = opts.post_run_script\n\tgut.color_output = !opts.disable_colors\n\tgut.show_orphans(!opts.hide_orphans)\n\tgut.junit_xml_file = opts.junit_xml_file\n\tgut.junit_xml_timestamp = opts.junit_xml_timestamp\n\tgut.paint_after = str(opts.paint_after).to_float()\n\tgut.wait_log_delay = opts.wait_log_delay\n\n\t# These error_tracker options default to true.  Don't trust this comment.\n\tif(!opts.failure_error_types.has(FAIL_ERROR_TYPE_ENGINE)):\n\t\tgut.error_tracker.treat_engine_errors_as = GutUtils.TREAT_AS.NOTHING\n\n\tif(!opts.failure_error_types.has(FAIL_ERROR_TYPE_PUSH_ERROR)):\n\t\tgut.error_tracker.treat_push_error_as = GutUtils.TREAT_AS.NOTHING\n\n\tif(!opts.failure_error_types.has(FAIL_ERROR_TYPE_GUT)):\n\t\tgut.error_tracker.treat_gut_errors_as = GutUtils.TREAT_AS.NOTHING\n\n\tgut.error_tracker.register_loggers = !opts.no_error_tracking\n\n\treturn gut\n\n\n# --------------------------\n# Public\n# --------------------------\nfunc write_options(path):\n\tvar content = JSON.stringify(options, ' ')\n\n\tvar f = FileAccess.open(path, FileAccess.WRITE)\n\tvar result = FileAccess.get_open_error()\n\tif(f != null):\n\t\tf.store_string(content)\n\t\tf = null # closes file\n\telse:\n\t\tlogger.error(str(\"Could not open file \", path, ' ', result))\n\treturn result\n\n\n# consistent name\nfunc save_file(path):\n\twrite_options(path)\n\n\nfunc load_options(path):\n\treturn _load_options_from_config_file(path, options)\n\n\n# consistent name\nfunc load_file(path):\n\treturn load_options(path)\n\n\nfunc load_options_no_defaults(path):\n\toptions = _null_copy(default_options)\n\treturn _load_options_from_config_file(path, options)\n\n\nfunc apply_options(gut):\n\t_apply_options(options, gut)\n\n\n\n\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n"
  },
  {
    "path": "demo/addons/gut/gut_config.gd.uid",
    "content": "uid://bobi58361x1ya\n"
  },
  {
    "path": "demo/addons/gut/gut_fonts.gd",
    "content": "# ------------------------------------------------------------------------------\n# There was an error that someone found in Godot 4.4.1, but ended up being a\n# different error in Godot 4.5.  The fix was to hold a reference to the font\n# so that TextEdit control did not lose the font when switching.  This is\n# the solution I came up with.  Just hold a reference to all fonts we use,\n# but only when we use them.  Basically a lazy loader with some semantics for\n# font names and location.\n#\n# https://github.com/bitwes/Gut/issues/749\n#\n# An instance of this could be used to allow users to specify their own fonts.\n# It's not perect for that yet, but it is feasible.\n# ------------------------------------------------------------------------------\nconst DEFAULT_CUSTOM_FONT_NAME = 'CourierPrime'\nconst THEME_FONT_TO_FONT_TYPES_MAP = {\n\t'font':FONT_TYPES.REGULAR,\n\t'normal_font': FONT_TYPES.REGULAR,\n\t'bold_font': FONT_TYPES.BOLD,\n\t'italics_font':FONT_TYPES.ITALIC,\n\t'bold_italics_font':FONT_TYPES.BOLD_ITALIC\n}\n\n\n# Values for FONT_TYPES are based on Google font file suffix (not extension).\n# A font file will be a key from fonts + - + FONT_TYPE value + .ttf.\nconst FONT_TYPES = {\n\tREGULAR = 'Regular',\n\tBOLD = 'Bold',\n\tITALIC = 'Italic',\n\tBOLD_ITALIC = 'BoldItalic'\n}\n\n\nvar fonts = {\n\t'AnonymousPro':{},\n\t'CourierPrime':{},\n\t'LobsterTwo':{},\n\t'Default':{}\n}\n\n\nvar custom_font_path = 'res://addons/gut/fonts/'\n\n\nfunc _init():\n\t_populate_default_fonts()\n\n\nfunc _populate_default_fonts():\n\tvar ctrl = TextEdit.new()\n\tvar f = ctrl.get_theme_font('font')\n\tfor key in FONT_TYPES:\n\t\tfonts['Default'][FONT_TYPES[key]] = f\n\tctrl.free()\n\n\nfunc _load_font(font_name, font_type, font_path):\n\tvar dynamic_font = FontFile.new()\n\tdynamic_font.load_dynamic_font(font_path)\n\tfonts[font_name][font_type] = dynamic_font\n\n\nfunc get_font(font_name, font_type='Regular'):\n\tif(!fonts.has(font_name)):\n\t\tpush_error(str(\"Invalid font name '\", font_name, \"'\"))\n\t\treturn fonts['Default'][FONT_TYPES.REGULAR]\n\n\tif(!FONT_TYPES.values().has(font_type)):\n\t\tpush_error(str(\"Invalid font type '\", font_type, \"'\"))\n\t\treturn fonts['Default'][FONT_TYPES.REGULAR]\n\n\tif(!fonts[font_name].has(font_type)):\n\t\tvar filename = custom_font_path.path_join(str(font_name, '-', font_type, '.ttf'))\n\t\tif(FileAccess.file_exists(filename)):\n\t\t\t_load_font(font_name, font_type, filename)\n\t\telse:\n\t\t\tpush_error(str(\"Missing custom font \", filename))\n\t\t\treturn fonts['Default'][FONT_TYPES.REGULAR]\n\n\treturn fonts.get(font_name, {}).get(font_type, null)\n\n\nfunc get_font_names():\n\treturn fonts.keys()\n\n\n# Maps the various theme font names (font, normal_font, italics_font etc) to\n# a FONT_TYPE.\nfunc get_font_for_theme_font_name(theme_font_name, custom_font_name):\n\tif(!THEME_FONT_TO_FONT_TYPES_MAP.has(theme_font_name)):\n\t\tpush_error(str(\"Unknown theme font name \", theme_font_name))\n\t\treturn get_font(custom_font_name)\n\treturn get_font(custom_font_name, THEME_FONT_TO_FONT_TYPES_MAP[theme_font_name])\n\n"
  },
  {
    "path": "demo/addons/gut/gut_fonts.gd.uid",
    "content": "uid://dvajwe2cllerq\n"
  },
  {
    "path": "demo/addons/gut/gut_loader.gd",
    "content": "# ------------------------------------------------------------------------------\n# This script should be loaded as soon as possible when running tests.  This\n# will disable warnings and then load all scripts that are registered with the\n# LazyLoader.\n#\n# Once you are ready to run tests, restore_ignore_addons should be called so\n# that it has the expected value.  This should be done after whatever loaded\n# this is done loading and doing setup stuff.\n#\n# This was created after a first attempt to suppress all GUT warnings did not\n# work for the strictest warning settings.  This has turned the LazyLoader into\n# just a Loader...so maybe all that should be reworked or renamed.  A problem\n# for a time when we are absolutely sure that all warnings are being correctly\n# suppressed I suppose.\n#\n# You can use the cli script test/resources/change_project_warnings.gd to\n# quickly alter project warning levels for testing purposes.\n# \tgdscript test/resources/change_project_warnings.gd --headless ++ -h\n#\n# You can set project warning settings from the command line with:\n#\tgodot -s addons/gut/cli/change_project_warnings.gd ++ -h\n#\n# This script should conform to, or ignore, the strictest warning settings.\n# ------------------------------------------------------------------------------\nconst WARNING_PATH : String = 'debug/gdscript/warnings/'\n\n\nstatic var were_addons_disabled : bool = true\n\n\n@warning_ignore(\"unsafe_method_access\")\n@warning_ignore(\"unsafe_property_access\")\n@warning_ignore(\"untyped_declaration\")\nstatic func _static_init() -> void:\n\twere_addons_disabled = ProjectSettings.get(str(WARNING_PATH, 'exclude_addons'))\n\tProjectSettings.set(str(WARNING_PATH, 'exclude_addons'), true)\n\n\tvar WarningsManager = load('res://addons/gut/warnings_manager.gd')\n\n\t# Turn everything back on (if it originally was on) if the warnings manager\n\t# is disabled.  This makes sure we see all the warnings for all the scripts\n\t# in the LazyLoader (except WarningsManager, but that's not a big deal).\n\t#\n\t# With the warnings manager disabled and all_warn warnings:\n\t#\ttest_warnings_manager.gd \t-> 5471 errors\n\t#\tfull run \t\t\t\t \t-> 131,742 errors\n\t#\n\t# With the warnings manager disabled and gut_default warnings:\n\t#\ttest_warnings_manager.gd \t-> 46 errors\n\t#\tfull run \t\t\t\t\t-> 165 errors.\n\tif(WarningsManager.disabled):\n\t\tProjectSettings.set(str(WARNING_PATH, 'exclude_addons'), were_addons_disabled)\n\n\t# Force a reference to utils.gd by path.  Using the class_name would cause\n\t# utils.gd to load when this script loads, before we could turn off the\n\t# warnings.\n\tvar _utils : Object = load('res://addons/gut/utils.gd')\n\n\t# Since load_all exists on the LazyLoader, it should be done now so nothing\n\t# sneaks in later...This essentially defeats the \"lazy\" part of the\n\t# LazyLoader, but not the \"loader\" part of LazyLoader.\n\t_utils.LazyLoader.load_all()\n\n\t# Make sure that the values set in WarningsManager's static_init actually\n\t# reflect the project settings and not whatever we do here to make things\n\t# not warn.\n\tWarningsManager._project_warnings.exclude_addons = were_addons_disabled\n\n\n# this can be called before tests are run to reinstate whatever exclude_addons\n# was set to before this script disabled it.\nstatic func restore_ignore_addons() -> void:\n\tProjectSettings.set(str(WARNING_PATH, 'exclude_addons'), were_addons_disabled)\n\n\n\n\n# ##############################################################################\n# (G)odot (U)nit (T)est class\n#\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n"
  },
  {
    "path": "demo/addons/gut/gut_loader.gd.uid",
    "content": "uid://2jdhrg7xws31\n"
  },
  {
    "path": "demo/addons/gut/gut_loader_the_scene.tscn",
    "content": "[gd_scene load_steps=2 format=3 uid=\"uid://jt6wsefn0x54\"]\n\n[sub_resource type=\"Resource\" id=\"Resource_cayac\"]\nmetadata/__load_path__ = \"res://addons/gut/gut_loader_the_scene.gd\"\n\n[node name=\"Node\" type=\"Node2D\"]\nscript = SubResource(\"Resource_cayac\")\n"
  },
  {
    "path": "demo/addons/gut/gut_menu.gd",
    "content": "var sub_menu : PopupMenu = null\n\nvar _menus = {\n\t# name : {\n\t# \tindex,\n\t# \tid,\n\t# \tcallback\n\t# }\n}\n\nsignal about\nsignal rerun\nsignal run_all\nsignal run_at_cursor\nsignal run_inner_class\nsignal run_script\nsignal run_test\nsignal show_gut\nsignal toggle_windowed\n\n\nfunc _init():\n\tsub_menu = PopupMenu.new()\n\tsub_menu.index_pressed.connect(_on_sub_menu_index_pressed)\n\tmake_menu()\n\n\nfunc _invalid_index():\n\tprint(\"bad menu index\")\n\n\nfunc _on_sub_menu_index_pressed(index):\n\tvar to_call : Callable = _invalid_index\n\tfor key in _menus:\n\t\tif(_menus[key].index == index):\n\t\t\tto_call = _menus[key].callback\n\n\tto_call.call()\n\n\nfunc add_menu(display_text, sig_to_emit, tooltip=''):\n\tvar index = sub_menu.item_count\n\t_menus[sig_to_emit.get_name()] = {\n\t\tindex = index,\n\t\tid = index,\n\t\tcallback = sig_to_emit.emit\n\t}\n\tsub_menu.add_item(display_text, index)\n\tsub_menu.set_item_tooltip(index, tooltip)\n\treturn index\n\n\n\nfunc make_menu():\n\tadd_menu(\"Toggle Windowed\", toggle_windowed, \n\t\t'Toggle GUT in the dock or a floating window')\n\tadd_menu(\"Show/Hide GUT\", show_gut, '')\n\n\tsub_menu.add_separator('Run')\n\tadd_menu(\"Run All\", run_all,\n\t\t\"Run all tests\")\n\tadd_menu(\"Run Script\", run_script,\n\t\t\"Run the currently selected script\")\n\tadd_menu(\"Run Inner Class\", run_inner_class,\n\t\t\"Run the currently selected inner test class\")\n\tadd_menu(\"Run Test\", run_test,\n\t\t\"Run the currently selected test\")\n\tadd_menu(\"Run At Cursor\", run_at_cursor,\n\t\t\"Run the most specific of script/inner class/test based on cursor position\")\n\tadd_menu(\"Rerun\", rerun, \"Rerun the last test(s) ran\", )\n\n\tsub_menu.add_separator()\n\tadd_menu(\"About\", about, 'All about GUT')\n\n\nfunc set_shortcut(menu_name, accel_or_input_key):\n\tif(typeof(accel_or_input_key) == TYPE_INT):\n\t\tsub_menu.set_item_accelerator(_menus[menu_name].index, accel_or_input_key)\n\telif(typeof(accel_or_input_key) == TYPE_OBJECT and accel_or_input_key is InputEventKey):\n\t\tsub_menu.set_item_accelerator(_menus[menu_name].index, accel_or_input_key.get_keycode_with_modifiers())\n\n\nfunc disable_menu(menu_name, disabled):\n\tsub_menu.set_item_disabled(_menus[menu_name].index, disabled)\n\n\nfunc apply_gut_shortcuts(shortcut_dialog):\n\tset_shortcut(\"show_gut\",\n\t\tshortcut_dialog.scbtn_panel.get_input_event())\n\tset_shortcut(\"run_all\",\n\t\tshortcut_dialog.scbtn_run_all.get_input_event())\n\tset_shortcut(\"run_script\",\n\t\tshortcut_dialog.scbtn_run_current_script.get_input_event())\n\tset_shortcut(\"run_inner_class\",\n\t\tshortcut_dialog.scbtn_run_current_inner.get_input_event())\n\tset_shortcut(\"run_test\",\n\t\tshortcut_dialog.scbtn_run_current_test.get_input_event())\n\tset_shortcut(\"run_at_cursor\",\n\t\tshortcut_dialog.scbtn_run_at_cursor.get_input_event())\n\tset_shortcut(\"rerun\",\n\t\tshortcut_dialog.scbtn_rerun.get_input_event())\n\tset_shortcut(\"toggle_windowed\",\n\t\tshortcut_dialog.scbtn_windowed.get_input_event())\n"
  },
  {
    "path": "demo/addons/gut/gut_menu.gd.uid",
    "content": "uid://crhdyu6u7n8c4\n"
  },
  {
    "path": "demo/addons/gut/gut_plugin.gd",
    "content": "@tool\nextends EditorPlugin\n\nvar VersionConversion = load(\"res://addons/gut/version_conversion.gd\")\nvar MenuManager = load(\"res://addons/gut/gut_menu.gd\")\nvar GutWindow = load(\"res://addons/gut/gui/GutEditorWindow.tscn\")\nvar BottomPanelScene = preload('res://addons/gut/gui/GutBottomPanel.tscn')\nvar GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')\n\nvar _bottom_panel : Control = null\nvar _menu_mgr = null\nvar _gut_button = null\nvar _gut_window = null\nvar _dock_mode = 'none'\n\n\nfunc _init():\n\tif(VersionConversion.error_if_not_all_classes_imported()):\n\t\treturn\n\n\nfunc _enter_tree():\n\tif(!_version_conversion()):\n\t\treturn\n\n\t_bottom_panel = BottomPanelScene.instantiate()\n\tgut_as_panel()\n\n\t# ---------\n\t# I removed this delay because it was causing issues with the shortcut button.\n\t# The shortcut button wouldn't work right until load_shortcuts is called., but\n\t# the delay gave you 3 seconds to click it before they were loaded.  This\n\t# await came with the conversion to 4 and probably isn't needed anymore.\n\t# I'm leaving it here becuase I don't know why it showed up to begin with\n\t# and if it's needed, it will be pretty hard to debug without seeing this.\n\t#\n\t# This should be deleted after the next release or two if not needed.\n\t#\n\t# I added it back in when doing the window stuff.  Starting in a window\n\t# made it angry (don't remember how) until I added it back in.\n\tawait get_tree().create_timer(1).timeout\n\t# ---\n\n\t_bottom_panel.set_interface(get_editor_interface())\n\t_bottom_panel.set_plugin(self)\n\t_bottom_panel.load_shortcuts()\n\n\t_menu_mgr = MenuManager.new()\n\t_bottom_panel._ctrls.run_at_cursor.menu_manager = _menu_mgr\n\t_bottom_panel.menu_manager = _menu_mgr\n\tadd_tool_submenu_item(\"GUT\", _menu_mgr.sub_menu)\n\n\tGutEditorGlobals.gut_plugin = self\n\n\n\nfunc _version_conversion():\n\tvar EditorGlobals = load(\"res://addons/gut/gui/editor_globals.gd\")\n\tEditorGlobals.create_temp_directory()\n\n\tif(VersionConversion.error_if_not_all_classes_imported()):\n\t\treturn false\n\n\tVersionConversion.convert()\n\treturn true\n\n\nfunc gut_as_window():\n\tif(_gut_window == null):\n\t\t_gut_window = GutWindow.instantiate()\n\t\t_gut_window.gut_plugin = self\n\t\tadd_child(_gut_window)\n\t\t_gut_window.theme = get_tree().root.theme\n\t\t_gut_window.interface = get_editor_interface()\n\n\t_gut_window.add_gut_panel(_bottom_panel)\n\t_bottom_panel.make_floating_btn.visible = false\n\t_gut_button = null\n\t_dock_mode = 'window'\n\n\nfunc gut_as_panel():\n\t_gut_button = add_control_to_bottom_panel(_bottom_panel, 'GUT')\n\t_bottom_panel.set_panel_button(_gut_button)\n\t_gut_button.shortcut_in_tooltip = true\n\t_dock_mode = 'panel'\n\t_bottom_panel._apply_shortcuts()\n\t_bottom_panel.results_horiz_layout()\n\t_bottom_panel.make_floating_btn.visible = true\n\n\tif(_gut_window != null):\n\t\t_gut_window.queue_free()\n\t\t_gut_window = null\n\n\nfunc toggle_windowed():\n\t_deparent_bottom_panel()\n\tif(_dock_mode == 'window' or _dock_mode == 'none'):\n\t\tgut_as_panel()\n\telif(_dock_mode == 'panel'):\n\t\tgut_as_window()\n\t_bottom_panel.show_me()\n\n\nfunc _deparent_bottom_panel():\n\tif(_dock_mode == 'window'):\n\t\t_gut_window.remove_panel()\n\telif(_dock_mode == 'panel'):\n\t\tremove_control_from_bottom_panel(_bottom_panel)\n\n\n\nfunc _exit_tree():\n\tremove_tool_menu_item(\"GUT\")\n\t_menu_mgr = null\n\tGutEditorGlobals.user_prefs.save_it()\n\t# Clean-up of the plugin goes here\n\t# Always remember to remove_at it from the engine when deactivated\n\t_deparent_bottom_panel()\n\tif(_gut_window != null):\n\t\t_gut_window.queue_free()\n\n\t_bottom_panel.menu_manager = null\n\t_bottom_panel.queue_free()\n\n\tremove_tool_menu_item(\"GUT\") # made by _menu_mgr\n\n\nfunc show_output_panel():\n\tif(_bottom_panel == null):\n\t\treturn\n\n\tvar panel = null\n\tvar kids = _bottom_panel.get_parent().get_children()\n\tvar idx = 0\n\n\twhile(idx < kids.size() and panel == null):\n\t\tif(str(kids[idx]).contains(\"<EditorLog#\")):\n\t\t\tpanel = kids[idx]\n\t\tidx += 1\n\n\tif(panel != null):\n\t\tmake_bottom_panel_item_visible(panel)"
  },
  {
    "path": "demo/addons/gut/gut_plugin.gd.uid",
    "content": "uid://7c5t72qt64mu\n"
  },
  {
    "path": "demo/addons/gut/gut_to_move.gd",
    "content": "# Temporary base script for gut.gd to hold the things to be remvoed and added\n# to some utility somewhere.\nextends Node\n\n# ------------------------------------------------------------------------------\n# deletes all files in a given directory\n# ------------------------------------------------------------------------------\nfunc directory_delete_files(path):\n\tvar d = DirAccess.open(path)\n\n\t# SHORTCIRCUIT\n\tif(d == null):\n\t\treturn\n\n\t# Traversing a directory is kinda odd.  You have to start the process of listing\n\t# the contents of a directory with list_dir_begin then use get_next until it\n\t# returns an empty string.  Then I guess you should end it.\n\td.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547\n\tvar thing = d.get_next() # could be a dir or a file or something else maybe?\n\tvar full_path = ''\n\twhile(thing != ''):\n\t\tfull_path = path + \"/\" + thing\n\t\t# file_exists returns fasle for directories\n\t\tif(d.file_exists(full_path)):\n\t\t\td.remove(full_path)\n\t\tthing = d.get_next()\n\n\td.list_dir_end()\n\n# ------------------------------------------------------------------------------\n# deletes the file at the specified path\n# ------------------------------------------------------------------------------\nfunc file_delete(path):\n\tvar d = DirAccess.open(path.get_base_dir())\n\tif(d != null):\n\t\td.remove(path)\n\n# ------------------------------------------------------------------------------\n# Checks to see if the passed in file has any data in it.\n# ------------------------------------------------------------------------------\nfunc is_file_empty(path):\n\tvar f = FileAccess.open(path, FileAccess.READ)\n\tvar result = FileAccess.get_open_error()\n\tvar empty = true\n\tif(result == OK):\n\t\tempty = f.get_length() == 0\n\tf = null\n\treturn empty\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc get_file_as_text(path):\n\treturn GutUtils.get_file_as_text(path)\n\n# ------------------------------------------------------------------------------\n# Creates an empty file at the specified path\n# ------------------------------------------------------------------------------\nfunc file_touch(path):\n\tFileAccess.open(path, FileAccess.WRITE)\n\n# ------------------------------------------------------------------------------\n# Simulate a number of frames by calling '_process' and '_physics_process' (if\n# the methods exist) on an object and all of its descendents. The specified frame\n# time, 'delta', will be passed to each simulated call.\n#\n# NOTE: Objects can disable their processing methods using 'set_process(false)' and\n# 'set_physics_process(false)'. This is reflected in the 'Object' methods\n# 'is_processing()' and 'is_physics_processing()', respectively. To make 'simulate'\n# respect this status, for example if you are testing an object which toggles\n# processing, pass 'check_is_processing' as 'true'.\n# ------------------------------------------------------------------------------\nfunc simulate(obj, times, delta, check_is_processing: bool = false):\n\tfor _i in range(times):\n\t\tif (\n\t\t\tobj.has_method(\"_process\")\n\t\t\tand (\n\t\t\t\tnot check_is_processing\n\t\t\t\tor obj.is_processing()\n\t\t\t)\n\t\t):\n\t\t\tobj._process(delta)\n\t\tif(\n\t\t\tobj.has_method(\"_physics_process\")\n\t\t\tand (\n\t\t\t\tnot check_is_processing\n\t\t\t\tor obj.is_physics_processing()\n\t\t\t)\n\t\t):\n\t\t\tobj._physics_process(delta)\n\n\t\tfor kid in obj.get_children():\n\t\t\tsimulate(kid, 1, delta, check_is_processing)\n"
  },
  {
    "path": "demo/addons/gut/gut_to_move.gd.uid",
    "content": "uid://d05irbtlqxtm6\n"
  },
  {
    "path": "demo/addons/gut/gut_tracked_error.gd",
    "content": "class_name GutTrackedError\n## This contains all the information provided by Godot about an error.\n## This is also used to represent a GUT error.  See [Logger] for\n## additional information about properties.  Some properties are not populated\n## for GUT errors.\n\n## This will be an [code]Array[ScriptBacktrace][/code] for engine/push errors.\n## This will the result of [code]get_stack[/code] for GUT errors.\nvar backtrace = []\n## Usually the description\nvar code = GutUtils.NO_TEST\nvar rationale = GutUtils.NO_TEST\n## [enum Logger.ErrorType] value or, for GUT errors, this will be [code skip-lint]GutUtils.GUT_ERROR_TYPE[/code].\nvar error_type = -1\nvar editor_notify = false\n\n## The full path to the file where the error occurred.\nvar file = GutUtils.NO_TEST\n## The function name in [member file] where the error occurred.\nvar function = GutUtils.NO_TEST\n## The line number in [member file]\nvar line = -1\n\n## Used by GUT to flag errors as being handled.  This is set by various asserts\n## or can be set in a test.  When set to [code]true[/code] GUT will ignore it\n## when determining if an unexpected error occurred during the execution of the\n## test.  Setting this value prior to performing any of the error related\n## asserts may have unexpected results.  It is recommended you either set this\n## manually or use the error asserts.\nvar handled = false\n\n\n## _to_string that is not _to_string.\nfunc to_s() -> String:\n\treturn str(\"CODE:\", code, \" TYPE:\", error_type, \" RATIONALE:\", rationale, \"\\n\",\n\t\tfile, '->', function, '@', line, \"\\n\",\n\t\tbacktrace, \"\\n\")\n\n\n## Returns [code]true[/code] if the error is a push_error.\nfunc is_push_error():\n\treturn error_type != GutUtils.GUT_ERROR_TYPE and function == \"push_error\"\n\n\n## Returns [code]true[/code] if the error is an engine error.  This includes\n## all errors that pass through the [Logger] that do not originate from the\n## [code]push_error[/code] function.\nfunc is_engine_error():\n\treturn error_type != GutUtils.GUT_ERROR_TYPE and !is_push_error()\n\n\n## Returns [code]true[/code] if the error is a GUT error.  Some fields may not\n## be populated for GUT errors.\nfunc is_gut_error():\n\treturn error_type == GutUtils.GUT_ERROR_TYPE\n\n\nfunc contains_text(text):\n\treturn code.to_lower().find(text.to_lower()) != -1 or \\\n\t\trationale.to_lower().find(text.to_lower()) != -1\n\n\n## For display purposes only, the actual value returned may change over time.\n## This returns a name for the error_type as far as this class is concerned.\n## Use the various [code]is_[/code] methods to check if an error is a certain\n## type.\nfunc get_error_type_name():\n\tvar to_return = \"Unknown\"\n\n\tif(is_gut_error()):\n\t\tto_return =  \"GUT\"\n\telif(is_push_error()):\n\t\tto_return = \"push_error\"\n\telif(is_engine_error()):\n\t\tto_return = str(\"engine-\", error_type)\n\n\treturn to_return\n\n\n# this might not work in other languages, and feels falkey, but might be\n# useful at some point.\n# func is_assert():\n# \treturn error_type == Logger.ERROR_TYPE_SCRIPT and \\\n# \t\t(code.find(\"Assertion failed.\") == 0 or \\\n# \t\t\tcode.find(\"Assertion failed:\") == 0)"
  },
  {
    "path": "demo/addons/gut/gut_tracked_error.gd.uid",
    "content": "uid://c1m2dbkoyf4fn\n"
  },
  {
    "path": "demo/addons/gut/gut_vscode_debugger.gd",
    "content": "# ------------------------------------------------------------------------------\n# Entry point for using the debugger through VSCode.  The gut-extension for\n# VSCode launches this instead of gut_cmdln.gd when running tests through the\n# debugger.\n#\n# This could become more complex overtime, but right now all we have to do is\n# to make sure the console printer is enabled or you do not get any output.\n# ------------------------------------------------------------------------------\nextends 'res://addons/gut/gut_cmdln.gd'\n\nfunc run_tests(runner):\n\trunner.get_gut().get_logger().disable_printer('console', false)\n\trunner.run_tests()\n\n\n# ##############################################################################\n#(G)odot (U)nit (T)est class\n#\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n"
  },
  {
    "path": "demo/addons/gut/gut_vscode_debugger.gd.uid",
    "content": "uid://hflec26434u5\n"
  },
  {
    "path": "demo/addons/gut/hook_script.gd",
    "content": "class_name GutHookScript\n## This script is the base for custom scripts to be used in pre and post\n## run hooks.\n##\n## GUT Wiki:  [url=https://gut.readthedocs.io]https://gut.readthedocs.io[/url]\n## [br][br]\n## Creating a hook script requires that you:[br]\n##  - Inherit [code skip-lint]GutHookScript[/code][br]\n##  - Implement a [code skip-lint]run()[/code] method[br]\n##  - Configure the path in GUT (gutconfig and/or editor) as the approparite hook (pre or post).[br]\n##\n## See [wiki]Hooks[/wiki]\n\n\n## Class responsible for generating xml.  You could use this to generate XML\n## yourself instead of using the built in GUT xml generation options.  See\n## [addons/gut/junit_xml_export.gd]\nvar JunitXmlExport = load('res://addons/gut/junit_xml_export.gd')\n\n## This is the instance of [GutMain] that is running the tests.  You can get\n## information about the run from this object.  This is set by GUT when the\n## script is instantiated.\nvar gut  = null\n\n# the exit code to be used by gut_cmdln.  See set method.\nvar _exit_code = null\n\nvar _should_abort =  false\n\n## Virtual method that will be called by GUT after instantiating this script.\n## This is where you put all of your logic.\nfunc run():\n\tgut.logger.error(\"Run method not overloaded.  Create a 'run()' method in your hook script to run your code.\")\n\n\n## Set the exit code when running from the command line.  If not set then the\n## default exit code will be returned (0 when no tests fail, 1 when any tests\n## fail).\nfunc set_exit_code(code : int):\n\t_exit_code  = code\n\n## Returns the exit code set with [code skip-lint]set_exit_code[/code]\nfunc get_exit_code():\n\treturn _exit_code\n\n## Usable by pre-run script to cause the run to end AFTER the run() method\n## finishes.  GUT will quit and post-run script will not be ran.\nfunc abort():\n\t_should_abort = true\n\n## Returns if [code skip-lint]abort[/code] was called.\nfunc should_abort():\n\treturn _should_abort\n"
  },
  {
    "path": "demo/addons/gut/hook_script.gd.uid",
    "content": "uid://1i8mr2knypot\n"
  },
  {
    "path": "demo/addons/gut/inner_class_registry.gd",
    "content": "var _registry = {}\n\n\nfunc _create_reg_entry(base_path, subpath):\n\tvar to_return = {\n\t\t\"base_path\":base_path,\n\t\t\"subpath\":subpath,\n\t\t\"base_resource\":load(base_path),\n\t\t\"full_path\":str(\"'\", base_path, \"'\", subpath)\n\t}\n\treturn to_return\n\nfunc _register_inners(base_path, obj, prev_inner = ''):\n\tvar const_map = obj.get_script_constant_map()\n\tvar consts = const_map.keys()\n\tvar const_idx = 0\n\n\twhile(const_idx < consts.size()):\n\t\tvar key = consts[const_idx]\n\t\tvar thing = const_map[key]\n\n\t\tif(typeof(thing) == TYPE_OBJECT and thing.resource_path == ''):\n\t\t\tvar cur_inner = str(prev_inner, \".\", key)\n\t\t\t_registry[thing] = _create_reg_entry(base_path, cur_inner)\n\t\t\t_register_inners(base_path, thing, cur_inner)\n\n\t\tconst_idx += 1\n\n\nfunc register(base_script):\n\tvar base_path = base_script.resource_path\n\t_register_inners(base_path, base_script)\n\n\nfunc get_extends_path(inner_class):\n\tif(_registry.has(inner_class)):\n\t\treturn _registry[inner_class].full_path\n\telse:\n\t\treturn null\n\n# returns the subpath for the inner class.  This includes the leading \".\" in\n# the path.\nfunc get_subpath(inner_class):\n\tif(_registry.has(inner_class)):\n\t\treturn _registry[inner_class].subpath\n\telse:\n\t\treturn ''\n\nfunc get_base_path(inner_class):\n\tif(_registry.has(inner_class)):\n\t\treturn _registry[inner_class].base_path\n\n\nfunc has(inner_class):\n\treturn _registry.has(inner_class)\n\n\nfunc get_base_resource(inner_class):\n\tif(_registry.has(inner_class)):\n\t\treturn _registry[inner_class].base_resource\n\n\nfunc to_s():\n\tvar text = \"\"\n\tfor key in _registry:\n\t\ttext += str(key, \": \", _registry[key], \"\\n\")\n\treturn text\n"
  },
  {
    "path": "demo/addons/gut/inner_class_registry.gd.uid",
    "content": "uid://l4hh1hhgq3kx\n"
  },
  {
    "path": "demo/addons/gut/input_factory.gd",
    "content": "class_name GutInputFactory\n## Static class full of helper methods to make InputEvent instances.\n##\n## This thing makes InputEvents.  Enjoy.\n\n# Implemented InputEvent* convenience methods\n# \tInputEventAction\n# \tInputEventKey\n# \tInputEventMouseButton\n# \tInputEventMouseMotion\n\n# Yet to implement InputEvents\n# \tInputEventJoypadButton\n# \tInputEventJoypadMotion\n# \tInputEventMagnifyGesture\n# \tInputEventMIDI\n# \tInputEventPanGesture\n# \tInputEventScreenDrag\n# \tInputEventScreenTouch\n\n\nstatic func _to_scancode(which):\n\tvar key_code = which\n\tif(typeof(key_code) == TYPE_STRING):\n\t\tkey_code = key_code.to_upper().to_ascii_buffer()[0]\n\treturn key_code\n\n\n## Creates a new button with the given propoerties.\nstatic func new_mouse_button_event(position, global_position, pressed, button_index) -> InputEventMouseButton:\n\tvar event = InputEventMouseButton.new()\n\tevent.position = position\n\tif(global_position != null):\n\t\tevent.global_position = global_position\n\tevent.pressed = pressed\n\tevent.button_index = button_index\n\n\treturn event\n\n\n## Returns an [InputEventKey] event with [code]pressed = false[/code].  [param which] can be a character or a [code]KEY_*[/code] constant.\nstatic func key_up(which) -> InputEventKey:\n\tvar event = InputEventKey.new()\n\tevent.keycode = _to_scancode(which)\n\tevent.pressed = false\n\treturn event\n\n\n## Returns an [InputEventKey] event with [code]pressed = true[/code].  [param which] can be a character or a [code]KEY_*[/code] constant.\nstatic func key_down(which) -> InputEventKey:\n\tvar event = InputEventKey.new()\n\tevent.keycode = _to_scancode(which)\n\tevent.pressed = true\n\treturn event\n\n\n## Returns an \"action up\" [InputEventAction] instance.  [param which] is the name of the action defined in the Key Map.\nstatic func action_up(which, strength=1.0) -> InputEventAction:\n\tvar event  = InputEventAction.new()\n\tevent.action = which\n\tevent.strength = strength\n\treturn event\n\n\n## Returns an \"action down\" [InputEventAction] instance.  [param which] is the name of the action defined in the Key Map.\nstatic func action_down(which, strength=1.0) -> InputEventAction:\n\tvar event  = InputEventAction.new()\n\tevent.action = which\n\tevent.strength = strength\n\tevent.pressed = true\n\treturn event\n\n\n## Returns a \"button down\" [InputEventMouseButton] for the left mouse button.\nstatic func mouse_left_button_down(position, global_position=null) -> InputEventMouseButton:\n\tvar event = new_mouse_button_event(position, global_position, true, MOUSE_BUTTON_LEFT)\n\treturn event\n\n\n## Returns a \"button up\" [InputEventMouseButton] for the left mouse button.\nstatic func mouse_left_button_up(position, global_position=null) -> InputEventMouseButton:\n\tvar event = new_mouse_button_event(position, global_position, false, MOUSE_BUTTON_LEFT)\n\treturn event\n\n\n## Returns a \"double click\" [InputEventMouseButton] for the left mouse button.\nstatic func mouse_double_click(position, global_position=null) -> InputEventMouseButton:\n\tvar event = new_mouse_button_event(position, global_position, false, MOUSE_BUTTON_LEFT)\n\tevent.double_click = true\n\treturn event\n\n\n## Returns a \"button down\" [InputEventMouseButton] for the right mouse button.\nstatic func mouse_right_button_down(position, global_position=null) -> InputEventMouseButton:\n\tvar event = new_mouse_button_event(position, global_position, true, MOUSE_BUTTON_RIGHT)\n\treturn event\n\n\n## Returns a \"button up\" [InputEventMouseButton] for the right mouse button.\nstatic func mouse_right_button_up(position, global_position=null) -> InputEventMouseButton:\n\tvar event = new_mouse_button_event(position, global_position, false, MOUSE_BUTTON_RIGHT)\n\treturn event\n\n\n## Returns a [InputEventMouseMotion] to move the mouse the specified positions.\nstatic func mouse_motion(position, global_position=null) -> InputEventMouseMotion:\n\tvar event = InputEventMouseMotion.new()\n\tevent.position = position\n\tif(global_position != null):\n\t\tevent.global_position = global_position\n\treturn event\n\n\n## Returns an [InputEventMouseMotion] that moves the mouse [param offset]\n## from the last [method mouse_motion] or [method mouse_motion] call.\nstatic func mouse_relative_motion(offset, last_motion_event=null, speed=Vector2(0, 0)) -> InputEventMouseMotion:\n\tvar event = null\n\tif(last_motion_event == null):\n\t\tevent = mouse_motion(offset)\n\t\tevent.velocity = speed\n\telse:\n\t\tevent = last_motion_event.duplicate()\n\t\tevent.position += offset\n\t\tevent.global_position += offset\n\t\tevent.relative = offset\n\t\tevent.velocity = speed\n\treturn event\n\n# ##############################################################################\n#(G)odot (U)nit (T)est class\n#\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n# Description\n# -----------\n# ##############################################################################\n"
  },
  {
    "path": "demo/addons/gut/input_factory.gd.uid",
    "content": "uid://b4s8333o8s4cp\n"
  },
  {
    "path": "demo/addons/gut/input_sender.gd",
    "content": "class_name GutInputSender\n## The GutInputSender class.  It sends input to places.\n##\n## [br][br]\n## GUT Wiki:  [url=https://gut.readthedocs.io]https://gut.readthedocs.io[/url][br]\n## See [wiki]Mocking-Input[/wiki] for examples.\n## [br][br]\n## This class can be used to send [code]InputEvent*[/code] events to various\n## objects.  It also allows you to script out a series of inputs and play\n## them back in real time.  You could use it to:[br]\n##  - Verify that jump height depends on how long the jump button is pressed.[br]\n##  - Double tap a direction performs a dash.[br]\n##  - Down, Down-Forward, Forward + punch throws a fireball.[br]\n## [br][br]\n## And much much more.\n## [br][br]\n## As of 9.3.1 you can use [code skip-lint]GutInputSender[/code] instead of [code]InputSender[/code].  It's the same thing, but [code skip-lint]GutInputSender[/code] is a [code]class_name[/code] so you may have less warnings and auto-complete will work.\n## [br][br]\n## [b]Warning[/b][br]\n## If you move the Godot window to a different monitor while tests are running it can cause input tests to fail.  [url=https://github.com/bitwes/Gut/issues/643]This issue[/url] has more details.\n\n\n\n# Implemented InputEvent* convenience methods\n# \tInputEventAction\n# \tInputEventKey\n# \tInputEventMouseButton\n#\tInputEventMouseMotion\n\n# Yet to implement InputEvents\n# \tInputEventJoypadButton\n# \tInputEventJoypadMotion\n# \tInputEventMagnifyGesture\n# \tInputEventMIDI\n# \tInputEventPanGesture\n# \tInputEventScreenDrag\n# \tInputEventScreenTouch\n\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nclass InputQueueItem:\n\textends Node\n\n\tvar events = []\n\tvar time_delay = null\n\tvar frame_delay = null\n\tvar _waited_frames = 0\n\tvar _is_ready = false\n\tvar _delay_started = false\n\n\tsignal event_ready\n\n\t# TODO should this be done in _physics_process instead or should it be\n\t# configurable?\n\tfunc _physics_process(delta):\n\t\tif(frame_delay > 0 and _delay_started):\n\t\t\t_waited_frames += 1\n\t\t\tif(_waited_frames >= frame_delay):\n\t\t\t\tevent_ready.emit()\n\n\tfunc _init(t_delay,f_delay):\n\t\ttime_delay = t_delay\n\t\tframe_delay = f_delay\n\t\t_is_ready = time_delay == 0 and frame_delay == 0\n\n\tfunc _on_time_timeout():\n\t\t_is_ready = true\n\t\tevent_ready.emit()\n\n\tfunc _delay_timer(t):\n\t\treturn Engine.get_main_loop().root.get_tree().create_timer(t)\n\n\tfunc is_ready():\n\t\treturn _is_ready\n\n\tfunc start():\n\t\t_delay_started = true\n\t\tif(time_delay > 0):\n\t\t\tvar t = _delay_timer(time_delay)\n\t\t\tt.connect(\"timeout\",Callable(self,\"_on_time_timeout\"))\n\n\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nclass MouseDraw:\n\textends Node2D\n\n\tvar down_color = Color(1, 1, 1, .25)\n\tvar up_color = Color(0, 0, 0, .25)\n\tvar line_color = Color(1, 0, 0)\n\tvar disabled = true :\n\t\tget : return disabled\n\t\tset(val) :\n\t\t\tdisabled = val\n\t\t\tqueue_redraw()\n\n\tvar _draw_at = Vector2(0, 0)\n\tvar _b1_down = false\n\tvar _b2_down = false\n\n\n\tfunc draw_event(event):\n\t\tif(event is InputEventMouse):\n\t\t\t_draw_at = event.position\n\t\t\tif(event is InputEventMouseButton):\n\t\t\t\tif(event.button_index == MOUSE_BUTTON_LEFT):\n\t\t\t\t\t_b1_down = event.pressed\n\t\t\t\telif(event.button_index == MOUSE_BUTTON_RIGHT):\n\t\t\t\t\t_b2_down = event.pressed\n\t\tqueue_redraw()\n\n\n\tfunc _draw_cicled_cursor():\n\t\tvar r = 10\n\t\tvar b1_color = up_color\n\t\tvar b2_color = up_color\n\n\t\tif(_b1_down):\n\t\t\tvar pos = _draw_at - (Vector2(r * 1.5, 0))\n\t\t\tdraw_arc(pos, r / 2, 0, 360, 180, b1_color)\n\n\t\tif(_b2_down):\n\t\t\tvar pos = _draw_at + (Vector2(r * 1.5, 0))\n\t\t\tdraw_arc(pos, r / 2, 0, 360, 180, b2_color)\n\n\t\tdraw_arc(_draw_at, r, 0, 360, 360, line_color, 1)\n\t\tdraw_line(_draw_at - Vector2(0, r), _draw_at + Vector2(0, r), line_color)\n\t\tdraw_line(_draw_at - Vector2(r, 0), _draw_at + Vector2(r, 0), line_color)\n\n\n\tfunc _draw_square_cursor():\n\t\tvar r = 10\n\t\tvar b1_color = up_color\n\t\tvar b2_color = up_color\n\n\t\tif(_b1_down):\n\t\t\tb1_color = down_color\n\n\t\tif(_b2_down):\n\t\t\tb2_color = down_color\n\n\t\tvar blen = r * .75\n\t\t# left button rectangle\n\t\tdraw_rect(Rect2(_draw_at - Vector2(blen, blen), Vector2(blen, blen * 2)), b1_color)\n\t\t# right button rectrangle\n\t\tdraw_rect(Rect2(_draw_at - Vector2(0, blen), Vector2(blen, blen * 2)), b2_color)\n\t\t# Crosshair\n\t\tdraw_line(_draw_at - Vector2(0, r), _draw_at + Vector2(0, r), line_color)\n\t\tdraw_line(_draw_at - Vector2(r, 0), _draw_at + Vector2(r, 0), line_color)\n\n\n\tfunc _draw():\n\t\tif(disabled):\n\t\t\treturn\n\t\t_draw_square_cursor()\n\n\n\n\n\n\n\n# ##############################################################################\n#\n# ##############################################################################\n## Local reference to the GutInputFactory static class\nconst INPUT_WARN = 'If using Input as a reciever it will not respond to *_down events until a *_up event is recieved.  Call the appropriate *_up event or use hold_for(...) to automatically release after some duration.'\n\nvar _lgr = GutUtils.get_logger()\nvar _receivers = []\nvar _input_queue = []\nvar _next_queue_item = null\n\n# used by hold_for and echo.\nvar _last_event = null\n# indexed by keycode, each entry contains a boolean value indicating the\n# last emitted \"pressed\" value for that keycode.\nvar _pressed_keys = {}\nvar _pressed_actions = {}\nvar _pressed_mouse_buttons = {}\n\nvar _auto_flush_input = false\nvar _tree_items_parent = null\nvar _mouse_draw = null;\n\nvar _default_mouse_position = {\n\tposition = Vector2(0, 0),\n\tglobal_position = Vector2(0, 0)\n}\n\nvar _last_mouse_position = {\n}\n\n## Warp mouse when sending InputEventMouse* events\nvar mouse_warp = false\n## Draw mouse position cross hairs.  Useful to see where the mouse is at\n## when not using [member mouse_warp]\nvar draw_mouse = true\n\n## Emitted when all events in the input queue have been sent.\nsignal idle\n\n\n## Accepts a single optional receiver.\nfunc _init(r=null):\n\tif(r != null):\n\t\tadd_receiver(r)\n\n\t_last_mouse_position = _default_mouse_position.duplicate()\n\t_tree_items_parent = Node.new()\n\tEngine.get_main_loop().root.add_child(_tree_items_parent)\n\n\t_mouse_draw = MouseDraw.new()\n\t_tree_items_parent.add_child(_mouse_draw)\n\t_mouse_draw.disabled = false\n\n\nfunc _notification(what):\n\tif(what == NOTIFICATION_PREDELETE):\n\t\tif(is_instance_valid(_tree_items_parent)):\n\t\t\t_tree_items_parent.queue_free()\n\n\nfunc _add_queue_item(item):\n\titem.connect(\"event_ready\", _on_queue_item_ready.bind(item))\n\t_next_queue_item = item\n\t_input_queue.append(item)\n\t_tree_items_parent.add_child(item)\n\tif(_input_queue.size() == 1):\n\t\titem.start()\n\n\nfunc _handle_pressed_keys(event):\n\tif(event is InputEventKey):\n\t\tif((event.pressed and !event.echo) and is_key_pressed(event.keycode)):\n\t\t\t_lgr.warn(str(\"InputSender:  key_down called for \", event.as_text(), \" when that key is already pressed.  \", INPUT_WARN))\n\t\t_pressed_keys[event.keycode] = event.pressed\n\telif(event is InputEventAction):\n\t\tif(event.pressed and is_action_pressed(event.action)):\n\t\t\t_lgr.warn(str(\"InputSender:  action_down called for \", event.action, \" when that action is already pressed.  \", INPUT_WARN))\n\t\t_pressed_actions[event.action] = event.pressed\n\telif(event is InputEventMouseButton):\n\t\tif(event.pressed and is_mouse_button_pressed(event.button_index)):\n\t\t\t_lgr.warn(str(\"InputSender:  mouse_button_down called for \", event.button_index, \" when that mouse button is already pressed.  \", INPUT_WARN))\n\t\t_pressed_mouse_buttons[event.button_index] = event\n\n\nfunc _handle_mouse_position(event):\n\tif(event is InputEventMouse):\n\t\t_mouse_draw.disabled = !draw_mouse\n\t\t_mouse_draw.draw_event(event)\n\t\tif(mouse_warp):\n\t\t\tDisplayServer.warp_mouse(event.position)\n\n\nfunc _send_event(event):\n\t_handle_mouse_position(event)\n\t_handle_pressed_keys(event)\n\n\tfor r in _receivers:\n\t\tif(r == Input):\n\t\t\tInput.parse_input_event(event)\n\t\t\tif(event is InputEventAction):\n\t\t\t\tif(event.pressed):\n\t\t\t\t\tInput.action_press(event.action)\n\t\t\t\telse:\n\t\t\t\t\tInput.action_release(event.action)\n\t\t\tif(_auto_flush_input):\n\t\t\t\tInput.flush_buffered_events()\n\t\telse:\n\t\t\tif(r.has_method(&\"_input\")):\n\t\t\t\tr._input(event)\n\n\t\t\tif(r.has_signal(&\"gui_input\")):\n\t\t\t\tr.gui_input.emit(event)\n\n\t\t\tif(r.has_method(&\"_gui_input\")):\n\t\t\t\tr._gui_input(event)\n\n\t\t\tif(r.has_method(&\"_unhandled_input\")):\n\t\t\t\tr._unhandled_input(event)\n\n\nfunc _send_or_record_event(event):\n\t_last_event = event\n\tif(_next_queue_item != null):\n\t\t_next_queue_item.events.append(event)\n\telse:\n\t\t_send_event(event)\n\n\nfunc _set_last_mouse_positions(event : InputEventMouse):\n\t_last_mouse_position.position = event.position\n\t_last_mouse_position.global_position = event.global_position\n\n\nfunc _apply_last_position_and_set_last_position(event, position, global_position):\n\tevent.position = GutUtils.nvl(position, _last_mouse_position.position)\n\tevent.global_position = GutUtils.nvl(\n\t\tglobal_position, _last_mouse_position.global_position)\n\t_set_last_mouse_positions(event)\n\n\nfunc _new_defaulted_mouse_button_event(position, global_position):\n\tvar event = InputEventMouseButton.new()\n\t_apply_last_position_and_set_last_position(event, position, global_position)\n\treturn event\n\n\nfunc _new_defaulted_mouse_motion_event(position, global_position):\n\tvar event = InputEventMouseMotion.new()\n\t_apply_last_position_and_set_last_position(event, position, global_position)\n\tfor key in _pressed_mouse_buttons:\n\t\tif(_pressed_mouse_buttons[key].pressed):\n\t\t\tevent.button_mask += key\n\treturn event\n\n\n# ------------------------------\n# Events\n# ------------------------------\nfunc _on_queue_item_ready(item):\n\tfor event in item.events:\n\t\t_send_event(event)\n\n\tvar done_event = _input_queue.pop_front()\n\tdone_event.queue_free()\n\n\tif(_input_queue.size() == 0):\n\t\t_next_queue_item = null\n\t\tidle.emit()\n\telse:\n\t\t_input_queue[0].start()\n\n\n# ------------------------------\n# Public\n# ------------------------------\n\n\n## Add an object to receive input events.\nfunc add_receiver(obj):\n\t_receivers.append(obj)\n\n\n## Returns the receivers that have been added.\nfunc get_receivers():\n\treturn _receivers\n\n\n## Returns true if the input queue has items to be processed, false if not.\nfunc is_idle():\n\treturn _input_queue.size() == 0\n\nfunc is_key_pressed(which):\n\tvar event = GutInputFactory.key_up(which)\n\treturn _pressed_keys.has(event.keycode) and _pressed_keys[event.keycode]\n\nfunc is_action_pressed(which):\n\treturn _pressed_actions.has(which) and _pressed_actions[which]\n\nfunc is_mouse_button_pressed(which):\n\treturn _pressed_mouse_buttons.has(which) and _pressed_mouse_buttons[which].pressed\n\n\n## Get the value of [method set_auto_flush_input].\nfunc get_auto_flush_input():\n\treturn _auto_flush_input\n\n\n## Enable/Disable auto flushing of input.  When enabled the [GutInputSender]\n## will call [code]Input.flush_buffered_events[/code] after each event is sent.\n## See the \"use_accumulated_input\" section in [wiki]Mocking-Input[/wiki] for more\n## information.\nfunc set_auto_flush_input(val):\n\t_auto_flush_input = val\n\n\n## Adds a delay between the last input queue item added and any queue item added\n## next.  By default this will wait [param t] seconds.  You can specify a\n## number of frames to wait by passing a string composed of a number and \"f\".\n## For example [code]wait(\"5f\")[/code] will wait 5 frames.\nfunc wait(t):\n\tif(typeof(t) == TYPE_STRING):\n\t\tvar suffix = t.substr(t.length() -1, 1)\n\t\tvar val = t.rstrip('s').rstrip('f').to_float()\n\n\t\tif(suffix.to_lower() == 's'):\n\t\t\twait_secs(val)\n\t\telif(suffix.to_lower() == 'f'):\n\t\t\twait_frames(val)\n\telse:\n\t\twait_secs(t)\n\n\treturn self\n\n\n## Clears the input queue and any state such as the last event sent and any\n## pressed actions/buttons.  Does not clear the list of receivers.\n## [br][br]\n## This should be done between each test when the [GutInputSender] is a class\n## level variable so that state does not leak between tests.\nfunc clear():\n\t_last_event = null\n\t_next_queue_item = null\n\n\tfor item in _input_queue:\n\t\titem.free()\n\t_input_queue.clear()\n\n\t_pressed_keys.clear()\n\t_pressed_actions.clear()\n\t_pressed_mouse_buttons.clear()\n\t_last_mouse_position = _default_mouse_position.duplicate()\n\n\n# ------------------------------\n# Event methods\n# ------------------------------\n\n## Sends a [InputEventKey] event with [code]pressed = false[/code].  [param which] can be a character or a [code]KEY_*[/code] constant.\nfunc key_up(which):\n\tvar event = GutInputFactory.key_up(which)\n\t_send_or_record_event(event)\n\treturn self\n\n\n## Sends a [InputEventKey] event with [code]pressed = true[/code].  [param which] can be a character or a [code]KEY_*[/code] constant.\nfunc key_down(which):\n\tvar event = GutInputFactory.key_down(which)\n\t_send_or_record_event(event)\n\treturn self\n\n\n## Sends an echo [InputEventKey] event of the last key event.\nfunc key_echo():\n\tif(_last_event != null and _last_event is InputEventKey):\n\t\tvar new_key = _last_event.duplicate()\n\t\tnew_key.echo = true\n\t\t_send_or_record_event(new_key)\n\treturn self\n\n\n## Sends a \"action up\" [InputEventAction] instance.  [param which] is the name of the action defined in the Key Map.\nfunc action_up(which, strength=1.0):\n\tvar event  = GutInputFactory.action_up(which, strength)\n\t_send_or_record_event(event)\n\treturn self\n\n\n## Sends a \"action down\" [InputEventAction] instance.  [param which] is the name of the action defined in the Key Map.\nfunc action_down(which, strength=1.0):\n\tvar event  = GutInputFactory.action_down(which, strength)\n\t_send_or_record_event(event)\n\treturn self\n\n\n## Sends a \"button down\" [InputEventMouseButton] for the left mouse button.\nfunc mouse_left_button_down(position=null, global_position=null):\n\tvar event = _new_defaulted_mouse_button_event(position, global_position)\n\tevent.pressed = true\n\tevent.button_index = MOUSE_BUTTON_LEFT\n\t_send_or_record_event(event)\n\treturn self\n\n\n## Sends a \"button up\" [InputEventMouseButton] for the left mouse button.\nfunc mouse_left_button_up(position=null, global_position=null):\n\tvar event = _new_defaulted_mouse_button_event(position, global_position)\n\tevent.pressed = false\n\tevent.button_index = MOUSE_BUTTON_LEFT\n\t_send_or_record_event(event)\n\treturn self\n\n\n## Sends a \"double click\" [InputEventMouseButton] for the left mouse button.\nfunc mouse_double_click(position=null, global_position=null):\n\tvar event = GutInputFactory.mouse_double_click(position, global_position)\n\tevent.double_click = true\n\t_send_or_record_event(event)\n\treturn self\n\n\n## Sends a \"button down\" [InputEventMouseButton] for the right mouse button.\nfunc mouse_right_button_down(position=null, global_position=null):\n\tvar event = _new_defaulted_mouse_button_event(position, global_position)\n\tevent.pressed = true\n\tevent.button_index = MOUSE_BUTTON_RIGHT\n\t_send_or_record_event(event)\n\treturn self\n\n\n## Sends a \"button up\" [InputEventMouseButton] for the right mouse button.\nfunc mouse_right_button_up(position=null, global_position=null):\n\tvar event = _new_defaulted_mouse_button_event(position, global_position)\n\tevent.pressed = false\n\tevent.button_index = MOUSE_BUTTON_RIGHT\n\t_send_or_record_event(event)\n\treturn self\n\n\n## Sends a [InputEventMouseMotion] to move the mouse the specified positions.\nfunc mouse_motion(position, global_position=null):\n\tvar event = _new_defaulted_mouse_motion_event(position, global_position)\n\t_send_or_record_event(event)\n\treturn self\n\n\n## Sends a [InputEventMouseMotion] that moves the mouse [param offset]\n## from the last [method mouse_motion] or [method mouse_set_position] call.\nfunc mouse_relative_motion(offset, speed=Vector2(0, 0)):\n\tvar last_event = _new_defaulted_mouse_motion_event(null, null)\n\tvar event = GutInputFactory.mouse_relative_motion(offset, last_event, speed)\n\t_set_last_mouse_positions(event)\n\t_send_or_record_event(event)\n\treturn self\n\n\n## Sets the mouse's position.  This does not send an event.  This position will\n## be used for the next call to [method mouse_relative_motion].\nfunc mouse_set_position(position, global_position=null):\n\tvar event = _new_defaulted_mouse_motion_event(position, global_position)\n\treturn self\n\n\n## Performs a left click at the given position.\nfunc mouse_left_click_at(where, duration = '5f'):\n\twait_frames(1)\n\tmouse_left_button_down(where)\n\thold_for(duration)\n\twait_frames(10)\n\treturn self\n\n\n## Create your own event and use this to send it to all receivers.\nfunc send_event(event):\n\t_send_or_record_event(event)\n\treturn self\n\n\n## Releases all [InputEventKey], [InputEventAction], and [InputEventMouseButton]\n## events that have passed through this instance.  These events could have been\n## generated via the various [code]_down[/code] methods or passed to\n## [method send_event].\n## [br][br]\n## This will send the \"release\" event ([code]pressed = false[/code]) to all\n## receivers.  This should be done between each test when using `Input` as a\n## receiver.\nfunc release_all():\n\tfor key in _pressed_keys:\n\t\tif(_pressed_keys[key]):\n\t\t\t_send_event(GutInputFactory.key_up(key))\n\t_pressed_keys.clear()\n\n\tfor key in _pressed_actions:\n\t\tif(_pressed_actions[key]):\n\t\t\t_send_event(GutInputFactory.action_up(key))\n\t_pressed_actions.clear()\n\n\tfor key in _pressed_mouse_buttons:\n\t\tvar event = _pressed_mouse_buttons[key].duplicate()\n\t\tif(event.pressed):\n\t\t\tevent.pressed = false\n\t\t\t_send_event(event)\n\t_pressed_mouse_buttons.clear()\n\n\treturn self\n\n## Same as [method wait] but only accepts a number of frames to wait.\nfunc wait_frames(num_frames):\n\tvar item = InputQueueItem.new(0, num_frames)\n\t_add_queue_item(item)\n\treturn self\n\n\n## Same as [method wait] but only accepts a number of seconds to wait.\nfunc wait_secs(num_secs):\n\tvar item = InputQueueItem.new(num_secs, 0)\n\t_add_queue_item(item)\n\treturn self\n\n\n## This is a special [method wait] that will emit the previous input queue item\n## with [code]pressed = false[/code] after a delay.  If you pass a number then\n## it will wait that many seconds.  You can also use the `\"4f\"` format to wait\n## a specific number of frames.\n## [br][br]\n## For example [code]sender.action_down('jump').hold_for(\"10f\")[/code] will\n## cause two [InputEventAction] instances to be sent.  The \"jump-down\" event\n## from [method action_down] and then a \"jump-up\" event after 10 frames.\nfunc hold_for(duration):\n\tif(_last_event != null and _last_event.pressed):\n\t\tvar next_event = _last_event.duplicate()\n\t\tnext_event.pressed = false\n\n\t\twait(duration)\n\t\tsend_event(next_event)\n\treturn self\n\n\n## Same as [method hold_for] but specifically holds for a number of physics\n## frames.\nfunc hold_frames(duration:int):\n\treturn hold_for(str(duration, 'f'))\n\n\n## Same as [method hold_for] but specifically holds for a number of seconds.\nfunc hold_seconds(duration:float):\n\treturn hold_for(duration)\n\n\n# ##############################################################################\n#(G)odot (U)nit (T)est class\n#\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n# Description\n# -----------\n# This class sends input to one or more recievers.  The receivers' _input,\n# _unhandled_input, and _gui_input are called sending InputEvent* events.\n# InputEvents can be sent via the helper methods or a custom made InputEvent\n# can be sent via send_event(...)\n#\n# ##############################################################################\n"
  },
  {
    "path": "demo/addons/gut/input_sender.gd.uid",
    "content": "uid://da3cy6yko53dk\n"
  },
  {
    "path": "demo/addons/gut/junit_xml_export.gd",
    "content": "## Creates an export of a test run in the JUnit XML format.\n##\n## More words needed?\n\nvar _exporter = GutUtils.ResultExporter.new()\n\n## @ignore should be private I think\nfunc indent(s, ind):\n\tvar to_return = ind + s\n\tto_return = to_return.replace(\"\\n\", \"\\n\" + ind)\n\treturn to_return\n\n# Wraps content in CDATA section because it may contain special characters\n# e.g. str(null) becomes <null> and can break XML parsing.\nfunc wrap_cdata(content):\n\treturn \"<![CDATA[\" + str(content) + \"]]>\"\n\n## @ignore should be private I think\nfunc add_attr(name, value):\n\treturn str(name, '=\"', value, '\" ')\n\n\nfunc _export_test_result(test):\n\tvar to_return = ''\n\n\t# Right now the pending and failure messages won't fit in the message\n\t# attribute because they can span multiple lines and need to be escaped.\n\tif(test.status == 'pending'):\n\t\tvar skip_tag = str(\"<skipped message=\\\"pending\\\">\", wrap_cdata(test.pending[0]), \"</skipped>\")\n\t\tto_return += skip_tag\n\telif(test.status == 'fail'):\n\t\tvar fail_tag = str(\"<failure message=\\\"failed\\\">\", wrap_cdata(test.failing[0]), \"</failure>\")\n\t\tto_return += fail_tag\n\n\treturn to_return\n\n\nfunc _export_tests(script_result, classname):\n\tvar to_return = \"\"\n\n\tfor key in script_result.keys():\n\t\tvar test = script_result[key]\n\t\tvar assert_count = test.passing.size() + test.failing.size()\n\t\tto_return += \"<testcase \"\n\t\tto_return += add_attr(\"name\", key)\n\t\tto_return += add_attr(\"assertions\", assert_count)\n\t\tto_return += add_attr(\"status\", test.status)\n\t\tto_return += add_attr(\"classname\", classname.replace(\"res://\", \"\"))\n\t\tto_return += add_attr(\"time\", test.time_taken)\n\t\tto_return += \">\\n\"\n\n\t\tto_return += _export_test_result(test)\n\n\t\tto_return += \"</testcase>\\n\"\n\n\treturn to_return\n\n\nfunc _sum_test_time(script_result, classname)->float:\n\tvar to_return := 0.0\n\n\tfor key in script_result.keys():\n\t\tvar test = script_result[key]\n\t\tto_return += test.time_taken\n\n\treturn to_return\n\n\nfunc _export_scripts(exp_results):\n\tvar to_return = \"\"\n\tfor key in exp_results.test_scripts.scripts.keys():\n\t\tvar s = exp_results.test_scripts.scripts[key]\n\t\tto_return += \"<testsuite \"\n\t\tto_return += add_attr(\"name\", key.replace(\"res://\", \"\"))\n\t\tto_return += add_attr(\"tests\", s.props.tests)\n\t\tto_return += add_attr(\"failures\", s.props.failures)\n\t\tto_return += add_attr(\"skipped\", s.props.pending)\n\t\tto_return += add_attr(\"time\", _sum_test_time(s.tests, key) )\n\t\tto_return += \">\\n\"\n\n\t\tto_return += indent(_export_tests(s.tests, key), \"    \")\n\n\t\tto_return += \"</testsuite>\\n\"\n\n\treturn to_return\n\n\n## Takes in an instance of GutMain and returns a string of XML representing the\n## results of the run.\nfunc get_results_xml(gut):\n\tvar exp_results = _exporter.get_results_dictionary(gut)\n\tvar to_return = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>' + \"\\n\"\n\tto_return += '<testsuites '\n\tto_return += add_attr(\"name\", 'GutTests')\n\tto_return += add_attr(\"failures\", exp_results.test_scripts.props.failures)\n\tto_return += add_attr('tests', exp_results.test_scripts.props.tests)\n\tto_return += \">\\n\"\n\n\tto_return += indent(_export_scripts(exp_results), \"  \")\n\n\tto_return += '</testsuites>'\n\treturn to_return\n\n\n## Takes in an instance of GutMain and writes the XML file to the specified\n## path\nfunc write_file(gut, path):\n\tvar xml = get_results_xml(gut)\n\n\tvar f_result = GutUtils.write_file(path, xml)\n\tif(f_result != OK):\n\t\tvar msg = str(\"Error:  \", f_result, \".  Could not create export file \", path)\n\t\tGutUtils.get_logger().error(msg)\n\n\treturn f_result\n\n"
  },
  {
    "path": "demo/addons/gut/junit_xml_export.gd.uid",
    "content": "uid://23m7uwtgs8nj\n"
  },
  {
    "path": "demo/addons/gut/lazy_loader.gd",
    "content": "@tool\n# ------------------------------------------------------------------------------\n# Static\n# ------------------------------------------------------------------------------\nstatic var usage_counter = load('res://addons/gut/thing_counter.gd').new()\nstatic var WarningsManager = load('res://addons/gut/warnings_manager.gd')\n\nstatic func load_all():\n\tfor key in usage_counter.things:\n\t\tkey.get_loaded()\n\n\nstatic func print_usage():\n\tfor key in usage_counter.things:\n\t\tprint(key._path, '  (', usage_counter.things[key], ')')\n\n\nstatic func clear():\n\tusage_counter.things.clear()\n\n# ------------------------------------------------------------------------------\n# Class\n# ------------------------------------------------------------------------------\nvar _loaded = null\nvar _path = null\n\nfunc _init(path):\n\t_path = path\n\tusage_counter.add_thing_to_count(self)\n\n\nfunc get_loaded():\n\tif(_loaded == null):\n\t\t_loaded = WarningsManager.load_script_ignoring_all_warnings(_path)\n\tusage_counter.add(self)\n\treturn _loaded\n\n\n\n"
  },
  {
    "path": "demo/addons/gut/lazy_loader.gd.uid",
    "content": "uid://rcaa7rmoaaub\n"
  },
  {
    "path": "demo/addons/gut/logger.gd",
    "content": "var types = {\n\tdebug = 'debug',\n\tdeprecated = 'deprecated',\n\texpected_error = 'expected_error',\n\terror = 'error',\n\tfailed = 'failed',\n\tinfo = 'info',\n\tnormal = 'normal',\n\torphan = 'orphan',\n\tpassed = 'passed',\n\tpending = 'pending',\n\trisky = 'risky',\n\twarn = 'warn',\n}\n\nvar fmts = {\n\tred = 'red',\n\tyellow = 'yellow',\n\tgreen = 'green',\n\tblue = 'blue',\n\n\tbold = 'bold',\n\tunderline = 'underline',\n\n\tnone = null\n}\n\nvar _type_data = {\n\ttypes.debug:\t\t{disp='DEBUG', \t\tenabled=true, fmt=fmts.bold},\n\ttypes.deprecated:\t{disp='DEPRECATED', enabled=true, fmt=fmts.none},\n\ttypes.error:\t\t{disp='ERROR', \t\tenabled=true, fmt=fmts.red},\n\ttypes.expected_error:\t{disp=\"ExpectedError\", enabled=true, fmt=fmts.blue},\n\ttypes.failed:\t\t{disp='Failed', \tenabled=true, fmt=fmts.red},\n\ttypes.info:\t\t\t{disp='INFO', \t\tenabled=true, fmt=fmts.bold},\n\ttypes.normal:\t\t{disp='NORMAL', \tenabled=true, fmt=fmts.none},\n\ttypes.orphan:\t\t{disp='Orphans',\tenabled=true, fmt=fmts.yellow},\n\ttypes.passed:\t\t{disp='Passed', \tenabled=true, fmt=fmts.green},\n\ttypes.pending:\t\t{disp='Pending',\tenabled=true, fmt=fmts.yellow},\n\ttypes.risky:\t\t{disp='Risky',\t\tenabled=true, fmt=fmts.yellow},\n\ttypes.warn:\t\t\t{disp='WARNING', \tenabled=true, fmt=fmts.yellow},\n}\n\nvar _logs = {\n\ttypes.warn: [],\n\ttypes.error: [],\n\ttypes.info: [],\n\ttypes.debug: [],\n\ttypes.deprecated: [],\n\ttypes.expected_error: [],\n\ttypes.failed: [],\n}\n\nvar _printers = {\n\tterminal = null,\n\tgui = null,\n\tconsole = null\n}\n\nvar _gut = null\nvar _indent_level = 0\nvar _min_indent_level = 0\nvar _indent_string = '    '\nvar _less_test_names = false\nvar _yield_calls = 0\nvar _last_yield_text = ''\n\n\n\n\nfunc _init():\n\t_printers.terminal = GutUtils.Printers.TerminalPrinter.new()\n\t_printers.console = GutUtils.Printers.ConsolePrinter.new()\n\t# There were some problems in the timing of disabling this at the right\n\t# time in gut_cmdln so it is disabled by default.  This is enabled\n\t# by plugin_control.gd based on settings.\n\t_printers.console.set_disabled(true)\n\n\nfunc _indent_text(text):\n\tvar to_return = text\n\tvar ending_newline = ''\n\n\tif(text.ends_with(\"\\n\")):\n\t\tending_newline = \"\\n\"\n\t\tto_return = to_return.left(to_return.length() -1)\n\n\tvar pad = get_indent()\n\tto_return = to_return.replace(\"\\n\", \"\\n\" + pad)\n\tto_return += ending_newline\n\n\treturn pad + to_return\n\n\nfunc _should_print_to_printer(key_name):\n\treturn _printers[key_name] != null and !_printers[key_name].get_disabled()\n\n\nfunc _print_test_name():\n\tif(_gut == null):\n\t\treturn\n\n\tvar cur_test = _gut.get_current_test_object()\n\tif(cur_test == null):\n\t\treturn false\n\n\tif(!cur_test.has_printed_name):\n\t\tvar param_text = ''\n\t\tif(cur_test.arg_count > 0):\n\t\t\t# Just an FYI, parameter_handler in gut might not be set yet so can't\n\t\t\t# use it here for cooler output.\n\t\t\tparam_text = '<parameterized>'\n\t\t_output(str('* ', cur_test.name, param_text, \"\\n\"))\n\t\tcur_test.has_printed_name = true\n\n\nfunc _output(text, fmt=null):\n\tfor key in _printers:\n\t\tif(_should_print_to_printer(key)):\n\t\t\t_printers[key].send(text, fmt)\n\n\nfunc _log(text, fmt=fmts.none):\n\t_print_test_name()\n\tvar indented = _indent_text(text)\n\t_output(indented, fmt)\n\n# ---------------\n# Get Methods\n# ---------------\nfunc get_warnings():\n\treturn get_log_entries(types.warn)\n\n\nfunc get_errors():\n\treturn get_log_entries(types.error)\n\n\nfunc get_infos():\n\treturn get_log_entries(types.info)\n\n\nfunc get_debugs():\n\treturn get_log_entries(types.debug)\n\n\nfunc get_deprecated():\n\treturn get_log_entries(types.deprecated)\n\n\nfunc get_count(log_type=null):\n\tvar count = 0\n\tif(log_type == null):\n\t\tfor key in _logs:\n\t\t\tcount += _logs[key].size()\n\telse:\n\t\tcount = _logs[log_type].size()\n\treturn count\n\n\nfunc get_log_entries(log_type):\n\treturn _logs[log_type]\n\n\nfunc get_indent():\n\tvar pad = ''\n\tfor i in range(_indent_level):\n\t\tpad += _indent_string\n\n\treturn pad\n\n\n# ---------------\n# Log methods\n# ---------------\nfunc _output_type(type, text):\n\tvar td = _type_data[type]\n\tif(!td.enabled):\n\t\t# if(_logs.has(type)):\n\t\t# \t_logs[type].append(text)\n\t\treturn\n\n\t_print_test_name()\n\tif(type != types.normal):\n\t\tif(_logs.has(type)):\n\t\t\t_logs[type].append(text)\n\n\t\tvar start = str('[', td.disp, ']')\n\t\tif(text != null and text != ''):\n\t\t\tstart += ':  '\n\t\telse:\n\t\t\tstart += ' '\n\t\tvar indented_start = _indent_text(start)\n\t\tvar indented_end = _indent_text(text)\n\t\tindented_end = indented_end.lstrip(_indent_string)\n\t\t_output(indented_start, td.fmt)\n\t\t_output(indented_end + \"\\n\")\n\n\nfunc _output_type_no_indent(type, text):\n\tvar td = _type_data[type]\n\tif(!td.enabled):\n\t\t# if(_logs.has(type)):\n\t\t# \t_logs[type].append(text)\n\t\treturn\n\n\t_print_test_name()\n\tif(type != types.normal):\n\t\tif(_logs.has(type)):\n\t\t\t_logs[type].append(text)\n\n\t\tvar start = str('[', td.disp, ']')\n\t\t_output(start, td.fmt)\n\t\t_output(text + \"\\n\")\n\n\nfunc debug(text):\n\t_output_type(types.debug, text)\n\n\n# supply some text or the name of the deprecated method and the replacement.\nfunc deprecated(text, alt_method=null):\n\tvar msg = text\n\tif(alt_method):\n\t\tmsg = str('The method ', text, ' is deprecated, use ', alt_method , ' instead.')\n\treturn _output_type(types.deprecated, msg)\n\n\nfunc error(text):\n\t_output_type(types.error, text)\n\t# Use the _gut one instead of GutUtils.get_error_tracker() for testing\n\t# purposes.  This probably means this should have its own reference but\n\t# that seems too difficult now.\n\tif(_gut != null):\n\t\t_gut.error_tracker.add_gut_error(text)\n\n\nfunc expected_error(text):\n\t_output_type_no_indent(types.expected_error, text)\n\n\nfunc failed(text):\n\t_output_type(types.failed, text)\n\n\nfunc info(text):\n\t_output_type(types.info, text)\n\n\nfunc orphan(text):\n\tvar td = _type_data[\"orphan\"]\n\tif(!td.enabled):\n\t\treturn\n\t_output(_indent_text(text), td.fmt)\n\t_output(\"\\n\")\n\t# _output_type(types.orphan, text)\n\n\nfunc passed(text):\n\t_output_type(types.passed, text)\n\n\nfunc pending(text):\n\t_output_type(types.pending, text)\n\n\nfunc risky(text):\n\t_output_type(types.risky, text)\n\n\nfunc warn(text):\n\t_output_type(types.warn, text)\n\n\nfunc log(text='', fmt=fmts.none):\n\tif(text == ''):\n\t\t_output(\"\\n\")\n\telse:\n\t\t_log(text + \"\\n\", fmt)\n\treturn null\n\n\nfunc lograw(text, fmt=fmts.none):\n\treturn _output(text, fmt)\n\n\n# Print the test name if we aren't skipping names of tests that pass (basically\n# what _less_test_names means))\nfunc log_test_name():\n\t# suppress output if we haven't printed the test name yet and\n\t# what to print is the test name.\n\tif(!_less_test_names):\n\t\t_print_test_name()\n\n# ---------------\n# Misc\n# ---------------\nfunc get_gut():\n\treturn _gut\n\n\nfunc set_gut(gut):\n\t_gut = gut\n\tif(_gut == null):\n\t\t_printers.gui = null\n\telse:\n\t\tif(_printers.gui == null):\n\t\t\t_printers.gui = GutUtils.Printers.GutGuiPrinter.new()\n\n\nfunc get_indent_level():\n\treturn _indent_level\n\n\nfunc set_indent_level(indent_level):\n\t_indent_level = max(_min_indent_level, indent_level)\n\n\nfunc get_indent_string():\n\treturn _indent_string\n\n\nfunc set_indent_string(indent_string):\n\t_indent_string = indent_string\n\n\nfunc clear():\n\tfor key in _logs:\n\t\t_logs[key].clear()\n\n\nfunc inc_indent():\n\t_indent_level += 1\n\n\nfunc dec_indent():\n\t_indent_level = max(_min_indent_level, _indent_level -1)\n\n\nfunc is_type_enabled(type):\n\treturn _type_data[type].enabled\n\n\nfunc set_type_enabled(type, is_enabled):\n\t_type_data[type].enabled = is_enabled\n\n\nfunc get_less_test_names():\n\treturn _less_test_names\n\n\nfunc set_less_test_names(less_test_names):\n\t_less_test_names = less_test_names\n\n\nfunc disable_printer(name, is_disabled):\n\tif(_printers[name] != null):\n\t\t_printers[name].set_disabled(is_disabled)\n\n\nfunc is_printer_disabled(name):\n\treturn _printers[name].get_disabled()\n\n\nfunc disable_formatting(is_disabled):\n\tfor key in _printers:\n\t\t_printers[key].set_format_enabled(!is_disabled)\n\n\nfunc disable_all_printers(is_disabled):\n\tfor p in _printers:\n\t\tdisable_printer(p, is_disabled)\n\n\nfunc get_printer(printer_key):\n\treturn _printers[printer_key]\n\n\nfunc _yield_text_terminal(text):\n\tvar printer = _printers['terminal']\n\tif(_yield_calls != 0):\n\t\tprinter.clear_line()\n\t\tprinter.back(_last_yield_text.length())\n\tprinter.send(text, fmts.yellow)\n\n\n# Format and printing rules for the \"Awaiting\" messages.\nfunc wait_msg(text):\n\tif(_type_data.warn.enabled):\n\t\tself.log(text, fmts.yellow)\n\n\nfunc get_gui_bbcode():\n\treturn _printers.gui.get_bbcode()\n\n\n\n# ##############################################################################\n#(G)odot (U)nit (T)est class\n#\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n# This class wraps around the various printers and supplies formatting for the\n# various message types (error, warning, etc).\n# ##############################################################################\n"
  },
  {
    "path": "demo/addons/gut/logger.gd.uid",
    "content": "uid://cqd3rmuu2drug\n"
  },
  {
    "path": "demo/addons/gut/menu_manager.gd.uid",
    "content": "uid://b3k81q304cji1\n"
  },
  {
    "path": "demo/addons/gut/method_maker.gd",
    "content": "class CallParameters:\n\tvar p_name = null\n\tvar default = null\n\tvar vararg = false\n\n\tfunc _init(n,d):\n\t\tp_name = n\n\t\tdefault = d\n\n\tfunc get_signature():\n\t\tif(vararg):\n\t\t\treturn \"...args: Array\"\n\t\telse:\n\t\t\treturn str(p_name, \"=\", default)\n\n\n# ------------------------------------------------------------------------------\n# This class will generate method declaration lines based on method meta\n# data.  It will create defaults that match the method data.\n#\n# --------------------\n# function meta data\n# --------------------\n# name:\n# flags:\n# args: [{\n# \t(class_name:),\n# \t(hint:0),\n# \t(hint_string:),\n# \t(name:),\n# \t(type:4),\n# \t(usage:7)\n# }]\n# default_args []\n\nvar _lgr = GutUtils.get_logger()\nconst PARAM_PREFIX = 'p_'\n\n# ------------------------------------------------------\n# _supported_defaults\n#\n# This array contains all the data types that are supported for default values.\n# If a value is supported it will contain either an empty string or a prefix\n# that should be used when setting the parameter default value.\n# For example int, real, bool do not need anything func(p1=1, p2=2.2, p3=false)\n# but things like Vectors and Colors do since only the parameters to create a\n# new Vector or Color are included in the metadata.\n# ------------------------------------------------------\n\t# TYPE_NIL = 0 — Variable is of type nil (only applied for null).\n\t# TYPE_BOOL = 1 — Variable is of type bool.\n\t# TYPE_INT = 2 — Variable is of type int.\n\t# TYPE_FLOAT = 3 — Variable is of type float/real.\n\t# TYPE_STRING = 4 — Variable is of type String.\n\t# TYPE_VECTOR2 = 5 — Variable is of type Vector2.\n\t# TYPE_RECT2 = 6 — Variable is of type Rect2.\n\t# TYPE_VECTOR3 = 7 — Variable is of type Vector3.\n\t# TYPE_COLOR = 14 — Variable is of type Color.\n\t# TYPE_OBJECT = 17 — Variable is of type Object.\n\t# TYPE_DICTIONARY = 18 — Variable is of type Dictionary.\n\t# TYPE_ARRAY = 19 — Variable is of type Array.\n\t# TYPE_PACKED_VECTOR2_ARRAY = 24 — Variable is of type PackedVector2Array.\n\t# TYPE_TRANSFORM3D = 13 — Variable is of type Transform3D.\n\t# TYPE_TRANSFORM2D = 8 — Variable is of type Transform2D.\n\t# TYPE_RID = 16 — Variable is of type RID.\n\t# TYPE_PACKED_INT32_ARRAY = 21 — Variable is of type PackedInt32Array.\n\t# TYPE_PACKED_FLOAT32_ARRAY = 22 — Variable is of type PackedFloat32Array.\n\t# TYPE_PACKED_STRING_ARRAY = 23 — Variable is of type PackedStringArray.\n\n\n# TYPE_PLANE = 9 — Variable is of type Plane.\n# TYPE_QUATERNION = 10 — Variable is of type Quaternion.\n# TYPE_AABB = 11 — Variable is of type AABB.\n# TYPE_BASIS = 12 — Variable is of type Basis.\n# TYPE_NODE_PATH = 15 — Variable is of type NodePath.\n# TYPE_PACKED_BYTE_ARRAY = 20 — Variable is of type PackedByteArray.\n# TYPE_PACKED_VECTOR3_ARRAY = 25 — Variable is of type PackedVector3Array.\n# TYPE_PACKED_COLOR_ARRAY = 26 — Variable is of type PackedColorArray.\n# TYPE_MAX = 27 — Marker for end of type constants.\n# ------------------------------------------------------\nvar _supported_defaults = []\n\nfunc _init():\n\tfor _i in range(TYPE_MAX):\n\t\t_supported_defaults.append(null)\n\n\t# These types do not require a prefix for defaults\n\t_supported_defaults[TYPE_NIL] = ''\n\t_supported_defaults[TYPE_BOOL] = ''\n\t_supported_defaults[TYPE_INT] = ''\n\t_supported_defaults[TYPE_FLOAT] = ''\n\t_supported_defaults[TYPE_OBJECT] = ''\n\t_supported_defaults[TYPE_ARRAY] = ''\n\t_supported_defaults[TYPE_STRING] = ''\n\t_supported_defaults[TYPE_STRING_NAME] = ''\n\t_supported_defaults[TYPE_DICTIONARY] = ''\n\t_supported_defaults[TYPE_PACKED_VECTOR2_ARRAY] = ''\n\t_supported_defaults[TYPE_RID] = ''\n\n\t# These require a prefix for whatever default is provided\n\t_supported_defaults[TYPE_VECTOR2] = 'Vector2'\n\t_supported_defaults[TYPE_VECTOR2I] = 'Vector2i'\n\t_supported_defaults[TYPE_RECT2] = 'Rect2'\n\t_supported_defaults[TYPE_RECT2I] = 'Rect2i'\n\t_supported_defaults[TYPE_VECTOR3] = 'Vector3'\n\t_supported_defaults[TYPE_COLOR] = 'Color'\n\t_supported_defaults[TYPE_TRANSFORM2D] = 'Transform2D'\n\t_supported_defaults[TYPE_TRANSFORM3D] = 'Transform3D'\n\t_supported_defaults[TYPE_PACKED_INT32_ARRAY] = 'PackedInt32Array'\n\t_supported_defaults[TYPE_PACKED_FLOAT32_ARRAY] = 'PackedFloat32Array'\n\t_supported_defaults[TYPE_PACKED_STRING_ARRAY] = 'PackedStringArray'\n\n# ###############\n# Private\n# ###############\nvar _func_text = GutUtils.get_file_as_text('res://addons/gut/double_templates/function_template.txt')\nvar _init_text = GutUtils.get_file_as_text('res://addons/gut/double_templates/init_template.txt')\n\nfunc _is_supported_default(type_flag):\n\treturn type_flag >= 0 and type_flag < _supported_defaults.size() and _supported_defaults[type_flag] != null\n\n\nfunc _make_stub_default(method, index):\n\treturn str('__gutdbl.default_val(\"', method, '\",', index, ')')\n\n\nfunc _make_arg_array(method_meta):\n\tvar to_return = []\n\n\tvar has_unsupported_defaults = false\n\n\tfor i in range(method_meta.args.size()):\n\t\tvar pname = method_meta.args[i].name\n\t\tvar dflt_text = _make_stub_default(method_meta.name, i)\n\t\tto_return.append(CallParameters.new(PARAM_PREFIX + pname, dflt_text))\n\n\tif(method_meta.flags & METHOD_FLAG_VARARG):\n\t\tvar cp = CallParameters.new(\"args\", \"\")\n\t\tcp.vararg = true\n\t\tto_return.append(cp)\n\n\treturn [has_unsupported_defaults, to_return];\n\n\n# Creates a list of parameters with defaults of null unless a default value is\n# found in the metadata.  If a default is found in the meta then it is used if\n# it is one we know how support.\n#\n# If a default is found that we don't know how to handle then this method will\n# return null.\nfunc _get_arg_text(arg_array):\n\tvar text = ''\n\n\tfor i in range(arg_array.size()):\n\t\ttext += arg_array[i].get_signature()\n\t\tif(i != arg_array.size() -1):\n\t\t\ttext += ', '\n\n\treturn text\n\n\n# creates a call to the function in meta in the super's class.\nfunc _get_super_call_text(method_name, args):\n\tvar params = ''\n\tfor i in range(args.size()):\n\t\tparams += args[i].p_name\n\t\tif(i != args.size() -1):\n\t\t\tparams += ', '\n\n\treturn str('await super(', params, ')')\n\n\nfunc _get_spy_call_parameters_text(args):\n\tvar called_with = 'null'\n\n\tif(args.size() > 0):\n\t\tcalled_with = '['\n\t\tfor i in range(args.size()):\n\t\t\tcalled_with += args[i].p_name\n\t\t\tif(i < args.size() - 1):\n\t\t\t\tcalled_with += ', '\n\t\tcalled_with += ']'\n\n\treturn called_with\n\n\n# ###############\n# Public\n# ###############\n\nfunc _get_init_text(meta, args, method_params, param_array):\n\tvar text = null\n\n\tvar decleration = str('func ', meta.name, '(', method_params, ')')\n\tvar super_params = ''\n\tif(args.size() > 0):\n\t\tfor i in range(args.size()):\n\t\t\tsuper_params += args[i].p_name\n\t\t\tif(i != args.size() -1):\n\t\t\t\tsuper_params += ', '\n\n\ttext = _init_text.format({\n\t\t\"func_decleration\":decleration,\n\t\t\"super_params\":super_params,\n\t\t\"param_array\":param_array,\n\t\t\"method_name\":meta.name,\n\t})\n\n\treturn text\n\n\n# Creates a delceration for a function based off of function metadata.  All\n# types whose defaults are supported will have their values.  If a datatype\n# is not supported and the parameter has a default, a warning message will be\n# printed and the declaration will return null.\nfunc get_function_text(meta, override_size=null):\n\tvar method_params = ''\n\tvar text = null\n\tvar result = _make_arg_array(meta)\n\tvar has_unsupported = result[0]\n\tvar args = result[1]\n\n\tvar param_array = _get_spy_call_parameters_text(args)\n\tif(has_unsupported):\n\t\t# This will cause a runtime error.  This is the most convenient way to\n\t\t# to stop running before the error gets more obscure.  _make_arg_array\n\t\t# generates a gut error when unsupported defaults are found.\n\t\tmethod_params = null\n\telse:\n\t\tmethod_params = _get_arg_text(args);\n\n\tif(param_array == 'null'):\n\t\tparam_array = '[]'\n\n\tif(method_params != null):\n\t\tif(meta.name == '_init'):\n\t\t\ttext =  _get_init_text(meta, args, method_params, param_array)\n\t\telse:\n\t\t\tvar decleration = str('func ', meta.name, '(', method_params, '):')\n\t\t\ttext = _func_text.format({\n\t\t\t\t\"func_decleration\":decleration,\n\t\t\t\t\"method_name\":meta.name,\n\t\t\t\t\"param_array\":param_array,\n\t\t\t\t\"super_call\":_get_super_call_text(meta.name, args),\n\t\t\t})\n\n\treturn text\n\n\nfunc get_logger():\n\treturn _lgr\n\n\nfunc set_logger(logger):\n\t_lgr = logger\n"
  },
  {
    "path": "demo/addons/gut/method_maker.gd.uid",
    "content": "uid://c6fxaf7bsbrp7\n"
  },
  {
    "path": "demo/addons/gut/one_to_many.gd",
    "content": "# ------------------------------------------------------------------------------\n# This datastructure represents a simple one-to-many relationship.  It manages\n# a dictionary of value/array pairs.  It ignores duplicates of both the \"one\"\n# and the \"many\".\n# ------------------------------------------------------------------------------\nvar items = {}\n\n# return the size of items or the size of an element in items if \"one\" was\n# specified.\nfunc size(one=null):\n\tvar to_return = 0\n\tif(one == null):\n\t\tto_return = items.size()\n\telif(items.has(one)):\n\t\tto_return = items[one].size()\n\treturn to_return\n\n\n# Add an element to \"one\" if it does not already exist\nfunc add(one, many_item):\n\tif(items.has(one)):\n\t\tif(!items[one].has(many_item)):\n\t\t\titems[one].append(many_item)\n\telse:\n\t\titems[one] = [many_item]\n\n\nfunc clear():\n\titems.clear()\n\n\nfunc has(one, many_item):\n\tvar to_return = false\n\tif(items.has(one)):\n\t\tto_return = items[one].has(many_item)\n\treturn to_return\n\n\nfunc to_s():\n\tvar to_return = ''\n\tfor key in items:\n\t\tto_return += str(key, \":  \", items[key], \"\\n\")\n\treturn to_return\n"
  },
  {
    "path": "demo/addons/gut/one_to_many.gd.uid",
    "content": "uid://cy3q0wsdgq515\n"
  },
  {
    "path": "demo/addons/gut/orphan_counter.gd",
    "content": "# ------------------------------------------------------------------------------\n# It keeps track of the orphans...so this is best name it could ever have.\n# ------------------------------------------------------------------------------\nclass Orphanage:\n\tconst UNGROUPED = \"Outside Tests\"\n\tconst SUBGROUP_SEP = '->'\n\n\tvar orphan_ids = {}\n\tvar oprhans_by_group = {}\n\tvar strutils = GutUtils.Strutils.new()\n\n\t# wrapper for stubbing\n\tfunc _get_system_orphan_node_ids():\n\t\treturn Node.get_orphan_node_ids()\n\n\n\tfunc _make_group_key(group=null, subgroup=null):\n\t\tvar to_return = UNGROUPED\n\t\tif(group != null):\n\t\t\tto_return = group\n\n\t\tif(subgroup == null):\n\t\t\tto_return += str(SUBGROUP_SEP, UNGROUPED)\n\t\telse:\n\t\t\tto_return += str(SUBGROUP_SEP, subgroup)\n\n\t\treturn to_return\n\n\n\tfunc _add_orphan_by_group(id, group, subgroup):\n\t\tvar key = _make_group_key(group, subgroup)\n\t\tif(oprhans_by_group.has(key)):\n\t\t\toprhans_by_group[key].append(id)\n\t\telse:\n\t\t\toprhans_by_group[key] = [id]\n\n\n\tfunc process_orphans(group=null, subgroup=null):\n\t\tvar new_orphans = []\n\t\tfor orphan_id in _get_system_orphan_node_ids():\n\t\t\tif(!orphan_ids.has(orphan_id)):\n\t\t\t\tnew_orphans.append(orphan_id)\n\t\t\t\torphan_ids[orphan_id] = {\n\t\t\t\t\t\"group\":GutUtils.nvl(group, UNGROUPED),\n\t\t\t\t\t\"subgroup\":GutUtils.nvl(subgroup, UNGROUPED),\n\t\t\t\t\t\"instance\":instance_from_id(orphan_id)\n\t\t\t\t}\n\t\t\t\t_add_orphan_by_group(orphan_id, group, subgroup)\n\n\t\treturn new_orphans\n\n\n\tfunc get_orphan_ids(group=null, subgroup=null):\n\t\tvar key = _make_group_key(group, subgroup)\n\t\treturn oprhans_by_group.get(key, [])\n\n\n\t# Given the likely size, this was way easier than making a dictionary\n\t# of dictionaries of arrays.\n\tfunc get_all_group_orphans(group):\n\t\tvar to_return = []\n\t\tfor key in oprhans_by_group:\n\t\t\tif(key == group or key.begins_with(str(group, SUBGROUP_SEP))):\n\t\t\t\tto_return.append_array(oprhans_by_group[key])\n\t\treturn to_return\n\n\n\t# clears out anything that is not still an orphan.\n\tfunc clean():\n\t\toprhans_by_group.clear()\n\t\tfor key in orphan_ids.keys():\n\t\t\tvar inst = orphan_ids[key].instance\n\t\t\tif(!is_instance_valid(inst) or inst.get_parent() != null and not orphan_ids.has(inst.get_parent().get_instance_id())):\n\t\t\t\torphan_ids.erase(key)\n\t\t\telse:\n\t\t\t\t_add_orphan_by_group(key, orphan_ids[key].group, orphan_ids[key].subgroup)\n\n\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nvar _strutils = GutStringUtils.new()\n\nvar orphanage : Orphanage = Orphanage.new()\nvar logger = GutUtils.get_logger()\nvar autofree = GutUtils.AutoFree.new()\n\n\nfunc _count_all_children(instance):\n\tvar count = instance.get_child_count()\n\tfor child in instance.get_children():\n\t\tcount += _count_all_children(child)\n\treturn count\n\n\nfunc get_orphan_list_text(orphan_ids):\n\tvar text = \"\"\n\tfor id in orphan_ids:\n\t\tvar kid_count_text = ''\n\t\tvar inst = orphanage.orphan_ids[id].instance\n\t\tif(is_instance_valid(inst) and inst.get_parent() == null):\n\t\t\tvar kid_count = _count_all_children(inst)\n\t\t\tif(kid_count != 0):\n\t\t\t\tkid_count_text = str(' + ', kid_count)\n\n\t\t\tvar autofree_text = ''\n\t\t\tif(autofree.has_instance_id(id)):\n\t\t\t\tautofree_text = (\" (autofree)\")\n\n\t\t\tif(text != ''):\n\t\t\t\ttext += \"\\n\"\n\t\t\ttext += str('* [', _strutils.type2str(inst), ']', kid_count_text, autofree_text)\n\n\treturn text\n\n\nfunc orphan_count() -> int:\n\treturn int(Performance.get_monitor(Performance.OBJECT_ORPHAN_NODE_COUNT))\n\n\nfunc record_orphans(group, subgroup = null):\n\treturn orphanage.process_orphans(group, subgroup)\n\n\nfunc convert_instance_ids_to_valid_instances(instance_ids):\n\tvar to_return = []\n\tfor entry in instance_ids:\n\t\tif(is_instance_id_valid(entry)):\n\t\t\tto_return.append(instance_from_id(entry))\n\treturn to_return\n\n\nfunc end_script(script_path, should_log):\n\trecord_orphans(script_path)\n\tvar orphans = orphanage.get_all_group_orphans(script_path)\n\tif(orphans.size() > 0 and should_log):\n\t\tlogger.orphan(str(orphans.size(), ' orphans'))\n\n\nfunc end_test(script_path, test_name, should_log = true):\n\trecord_orphans(script_path, test_name)\n\torphanage.clean()\n\t# Must get all the orphans and not just the results of record_orphans\n\t# because record_orphans may have been called for this group/subgroup\n\t# already.\n\tvar orphans = get_orphan_ids(script_path, test_name)\n\tif(orphans.size() > 0 and should_log):\n\t\tlogger.orphan(str(orphans.size(), ' Orphans'))\n\t\tlogger.inc_indent()\n\t\tlogger.orphan(get_orphan_list_text(orphans))\n\t\tlogger.dec_indent()\n\n\nfunc get_orphan_ids(group=null, subgroup=null):\n\tvar ids = []\n\tif(group == null):\n\t\tids = orphanage.orphan_ids.keys()\n\telif(subgroup == null):\n\t\tids = orphanage.get_all_group_orphans(group)\n\telse:\n\t\tids = orphanage.get_orphan_ids(group, subgroup)\n\n\treturn ids\n\n\nfunc get_count() -> int:\n\treturn orphan_count()\n\n\nfunc log_all():\n\tvar last_script = ''\n\tvar last_test = ''\n\n\tfor id in orphanage.orphan_ids:\n\t\tvar entry = orphanage.orphan_ids[id]\n\n\t\tif(last_script != entry.group):\n\t\t\tlast_script = entry.group\n\t\t\tlast_test = ''\n\t\t\tlogger.log(entry.group)\n\n\t\tif(last_test != entry.subgroup):\n\t\t\tlogger.inc_indent()\n\t\t\tlogger.log(str('- ', entry.subgroup))\n\t\t\tlast_test = entry.subgroup\n\t\t\tlogger.inc_indent()\n\t\t\tvar orphan_ids = orphanage.get_orphan_ids(last_script, last_test)\n\t\t\tlogger.orphan(get_orphan_list_text(orphan_ids))\n\t\t\tlogger.dec_indent()\n\t\t\tlogger.dec_indent()\n\n\n# ##############################################################################\n#(G)odot (U)nit (T)est class\n#\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n"
  },
  {
    "path": "demo/addons/gut/orphan_counter.gd.uid",
    "content": "uid://cgoysoitlbudy\n"
  },
  {
    "path": "demo/addons/gut/parameter_factory.gd",
    "content": "## Creates parameter structures for parameterized tests.\n##\n## This is a static class accessible in a [GutTest] script through\n## [member GutTest.ParameterFactory].  It contains methods for constructing parameters to be\n## used in parameterized tests.  It currently only has one, if you have anyu\n## ideas for more, make an issue.  More of them would be great since I prematurely\n## decided to make this static class and it has such a long name.  I'd feel a lot\n## better about it if there was more in here.\n## [br]\n## Additional Helper Ideas?[br]\n## [li]File.  IDK what it would look like.  csv maybe.[/li]\n## [li]Random values within a range?[/li]\n## [li]All int values in a range or add an optioanal step.[/li]\n\n\n\n\n## Creates an array of dictionaries.  It pairs up the names array with each set\n## of values in values.  If more names than values are specified then the missing\n## values will be filled with nulls.  If more values than names are specified\n## those values will be ignored.\n##\n## Example:\n##[codeblock]\n## create_named_parameters(['a', 'b'], [[1, 2], ['one', 'two']]) returns\n##    [{a:1, b:2}, {a:'one', b:'two'}]\n##[/codeblock]\n## [br]\n## This allows you to increase readability of your parameterized tests:\n## [br]\n##[codeblock]\n## var params = create_named_parameters(['a', 'b'], [[1, 2], ['one', 'two']])\n## func test_foo(p = use_parameters(params)):\n##    assert_eq(p.a, p.b)\n##[/codeblock]\n## [br]\n## Parameters:[br]\n##[li]names:  an array of names to be used as keys in the dictionaries[/li]\n##[li]values:  an array of arrays of values.[/li]\nstatic func named_parameters(names, values):\n\tvar named = []\n\tfor i in range(values.size()):\n\t\tvar entry = {}\n\n\t\tvar parray = values[i]\n\t\tif(typeof(parray) != TYPE_ARRAY):\n\t\t\tparray = [values[i]]\n\n\t\tfor j in range(names.size()):\n\t\t\tif(j >= parray.size()):\n\t\t\t\tentry[names[j]] = null\n\t\t\telse:\n\t\t\t\tentry[names[j]] = parray[j]\n\t\tnamed.append(entry)\n\n\treturn named\n\n\n\n# ##############################################################################\n#(G)odot (U)nit (T)est class\n#\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n# This is the home for all parameter creation helpers.  These functions should\n# all return an array of values to be used as parameters for parameterized\n# tests.\n# ##############################################################################"
  },
  {
    "path": "demo/addons/gut/parameter_factory.gd.uid",
    "content": "uid://c0e08oi55x8qj\n"
  },
  {
    "path": "demo/addons/gut/parameter_handler.gd",
    "content": "var _params = null\nvar _call_count = 0\nvar _logger = null\n\nfunc _init(params=null):\n\t_params = params\n\t_logger = GutUtils.get_logger()\n\tif(typeof(_params) != TYPE_ARRAY):\n\t\t_logger.error('You must pass an array to parameter_handler constructor.')\n\t\t_params = null\n\n\nfunc next_parameters():\n\t_call_count += 1\n\treturn _params[_call_count -1]\n\nfunc get_current_parameters():\n\treturn _params[_call_count]\n\nfunc is_done():\n\tvar done = true\n\tif(_params != null):\n\t\tdone = _call_count == _params.size()\n\treturn done\n\nfunc get_logger():\n\treturn _logger\n\nfunc set_logger(logger):\n\t_logger = logger\n\nfunc get_call_count():\n\treturn _call_count\n\nfunc get_parameter_count():\n\treturn _params.size()\n"
  },
  {
    "path": "demo/addons/gut/parameter_handler.gd.uid",
    "content": "uid://ba87ra5ep18wa\n"
  },
  {
    "path": "demo/addons/gut/plugin.cfg",
    "content": "[plugin]\n\nname=\"Gut\"\ndescription=\"Unit Testing tool for Godot.\"\nauthor=\"Butch Wesley\"\nversion=\"9.5.0\"\nscript=\"gut_plugin.gd\"\n"
  },
  {
    "path": "demo/addons/gut/printers.gd",
    "content": "# ------------------------------------------------------------------------------\n# Interface and some basic functionality for all printers.\n# ------------------------------------------------------------------------------\nclass Printer:\n\tvar _format_enabled = true\n\tvar _disabled = false\n\tvar _printer_name = 'NOT SET'\n\tvar _show_name = false # used for debugging, set manually\n\n\tfunc get_format_enabled():\n\t\treturn _format_enabled\n\n\tfunc set_format_enabled(format_enabled):\n\t\t_format_enabled = format_enabled\n\n\tfunc send(text, fmt=null):\n\t\tif(_disabled):\n\t\t\treturn\n\n\t\tvar formatted = text\n\t\tif(fmt != null and _format_enabled):\n\t\t\tformatted = format_text(text, fmt)\n\n\t\tif(_show_name):\n\t\t\tformatted = str('(', _printer_name, ')') + formatted\n\n\t\t_output(formatted)\n\n\tfunc get_disabled():\n\t\treturn _disabled\n\n\tfunc set_disabled(disabled):\n\t\t_disabled = disabled\n\n\t# --------------------\n\t# Virtual Methods (some have some default behavior)\n\t# --------------------\n\tfunc _output(text):\n\t\tpass\n\n\tfunc format_text(text, fmt):\n\t\treturn text\n\n# ------------------------------------------------------------------------------\n# Responsible for sending text to a GUT gui.\n# ------------------------------------------------------------------------------\nclass GutGuiPrinter:\n\textends Printer\n\tvar _textbox = null\n\n\tvar _colors = {\n\t\t\tred = Color.RED,\n\t\t\tyellow = Color.YELLOW,\n\t\t\tgreen = Color.GREEN,\n\t\t\tblue = Color.BLUE\n\t}\n\n\tfunc _init():\n\t\t_printer_name = 'gui'\n\n\tfunc _wrap_with_tag(text, tag):\n\t\treturn str('[', tag, ']', text, '[/', tag, ']')\n\n\tfunc _color_text(text, c_word):\n\t\treturn '[color=' + c_word + ']' + text + '[/color]'\n\n\t# Remember, we have to use push and pop because the output from the tests\n\t# can contain [] in it which can mess up the formatting.  There is no way\n\t# as of 3.4 that you can get the bbcode out of RTL when using push and pop.\n\t#\n\t# The only way we could get around this is by adding in non-printable\n\t# whitespace after each \"[\" that is in the text.  Then we could maybe do\n\t# this another way and still be able to get the bbcode out, or generate it\n\t# at the same time in a buffer (like we tried that one time).\n\t#\n\t# Since RTL doesn't have good search and selection methods, and those are\n\t# really handy in the editor, it isn't worth making bbcode that can be used\n\t# there as well.\n\t#\n\t# You'll try to get it so the colors can be the same in the editor as they\n\t# are in the output.  Good luck, and I hope I typed enough to not go too\n\t# far that rabbit hole before finding out it's not worth it.\n\tfunc format_text(text, fmt):\n\t\tif(_textbox == null):\n\t\t\treturn\n\n\t\tif(fmt == 'bold'):\n\t\t\t_textbox.push_bold()\n\t\telif(fmt == 'underline'):\n\t\t\t_textbox.push_underline()\n\t\telif(_colors.has(fmt)):\n\t\t\t_textbox.push_color(_colors[fmt])\n\t\telse:\n\t\t\t# just pushing something to pop.\n\t\t\t_textbox.push_normal()\n\n\t\t_textbox.add_text(text)\n\t\t_textbox.pop()\n\n\t\treturn ''\n\n\tfunc _output(text):\n\t\tif(_textbox == null):\n\t\t\treturn\n\n\t\t_textbox.add_text(text)\n\n\tfunc get_textbox():\n\t\treturn _textbox\n\n\tfunc set_textbox(textbox):\n\t\t_textbox = textbox\n\n\t# This can be very very slow when the box has a lot of text.\n\tfunc clear_line():\n\t\t_textbox.remove_line(_textbox.get_line_count() - 1)\n\t\t_textbox.queue_redraw()\n\n\tfunc get_bbcode():\n\t\treturn _textbox.text\n\n\tfunc get_disabled():\n\t\treturn _disabled and _textbox != null\n\n# ------------------------------------------------------------------------------\n# This AND TerminalPrinter should not be enabled at the same time since it will\n# result in duplicate output.  printraw does not print to the console so i had\n# to make another one.\n# ------------------------------------------------------------------------------\nclass ConsolePrinter:\n\textends Printer\n\tvar _buffer = ''\n\n\tfunc _init():\n\t\t_printer_name = 'console'\n\n\t# suppresses output until it encounters a newline to keep things\n\t# inline as much as possible.\n\tfunc _output(text):\n\t\tif(text.ends_with(\"\\n\")):\n\t\t\tprint(_buffer + text.left(text.length() -1))\n\t\t\t_buffer = ''\n\t\telse:\n\t\t\t_buffer += text\n\n# ------------------------------------------------------------------------------\n# Prints text to terminal, formats some words.\n# ------------------------------------------------------------------------------\nclass TerminalPrinter:\n\textends Printer\n\n\tvar escape = PackedByteArray([0x1b]).get_string_from_ascii()\n\tvar cmd_colors  = {\n\t\tred = escape + '[31m',\n\t\tyellow = escape + '[33m',\n\t\tgreen = escape + '[32m',\n\t\tblue = escape + '[34m',\n\n\t\tunderline = escape + '[4m',\n\t\tbold = escape + '[1m',\n\n\t\tdefault = escape + '[0m',\n\n\t\tclear_line = escape + '[2K'\n\t}\n\n\tfunc _init():\n\t\t_printer_name = 'terminal'\n\n\tfunc _output(text):\n\t\t# Note, printraw does not print to the console.\n\t\tprintraw(text)\n\n\tfunc format_text(text, fmt):\n\t\treturn cmd_colors[fmt] + text + cmd_colors.default\n\n\tfunc clear_line():\n\t\tsend(cmd_colors.clear_line)\n\n\tfunc back(n):\n\t\tsend(escape + str('[', n, 'D'))\n\n\tfunc forward(n):\n\t\tsend(escape + str('[', n, 'C'))\n"
  },
  {
    "path": "demo/addons/gut/printers.gd.uid",
    "content": "uid://nijvqplhkwjc\n"
  },
  {
    "path": "demo/addons/gut/result_exporter.gd",
    "content": "# ------------------------------------------------------------------------------\n# Creates a structure that contains all the data about the results of running\n# tests.  This was created to make an intermediate step organizing the result\n# of a run and exporting it in a specific format.  This can also serve as a\n# unofficial GUT export format.\n# ------------------------------------------------------------------------------\nvar json = JSON.new()\nvar strutils = GutStringUtils.new()\n\nfunc _export_tests(gut, collected_script):\n\tvar to_return = {}\n\tvar tests = collected_script.tests\n\tfor test in tests:\n\t\tif(test.get_status_text() != GutUtils.TEST_STATUSES.NOT_RUN):\n\t\t\tvar orphans = gut.get_orphan_counter().get_orphan_ids(\n\t\t\t\tcollected_script.get_filename_and_inner(),\n\t\t\t\ttest.name)\n\t\t\tvar orphan_node_strings = []\n\t\t\tfor o in orphans:\n\t\t\t\tif(is_instance_id_valid(o)):\n\t\t\t\t\torphan_node_strings.append(strutils.type2str(instance_from_id(o)))\n\n\t\t\tto_return[test.name] = {\n\t\t\t\t\"status\":test.get_status_text(),\n\t\t\t\t\"passing\":test.pass_texts,\n\t\t\t\t\"failing\":test.fail_texts,\n\t\t\t\t\"pending\":test.pending_texts,\n\t\t\t\t\"orphan_count\":orphan_node_strings.size(),\n\t\t\t\t\"orphans\":orphan_node_strings,\n\t\t\t\t\"time_taken\": test.time_taken\n\t\t\t}\n\n\treturn to_return\n\n# TODO\n#\terrors\nfunc _export_scripts(gut):\n\tvar collector = gut.get_test_collector()\n\tif(collector == null):\n\t\treturn {}\n\n\tvar scripts = {}\n\n\tfor s in collector.scripts:\n\t\tvar test_data = _export_tests(gut, s)\n\t\tscripts[s.get_full_name()] = {\n\t\t\t'props':{\n\t\t\t\t\"tests\":test_data.keys().size(),\n\t\t\t\t\"pending\":s.get_pending_count(),\n\t\t\t\t\"failures\":s.get_fail_count(),\n\t\t\t\t\"skipped\":s.was_skipped,\n\t\t\t},\n\t\t\t\"tests\":test_data\n\t\t}\n\treturn scripts\n\nfunc _make_results_dict():\n\tvar result =  {\n\t\t'test_scripts':{\n\t\t\t\"props\":{\n\t\t\t\t\"pending\":0,\n\t\t\t\t\"failures\":0,\n\t\t\t\t\"passing\":0,\n\t\t\t\t\"tests\":0,\n\t\t\t\t\"time\":0,\n\t\t\t\t\"orphans\":0,\n\t\t\t\t\"errors\":0,\n\t\t\t\t\"warnings\":0,\n\t\t\t\t\"risky\":0\n\t\t\t},\n\t\t\t\"scripts\":[]\n\t\t}\n\t}\n\treturn result\n\n\nfunc get_results_dictionary(gut, include_scripts=true):\n\tvar scripts = []\n\n\tif(include_scripts):\n\t\tscripts = _export_scripts(gut)\n\n\tvar result =  _make_results_dict()\n\n\tvar totals = gut.get_summary().get_totals()\n\n\tvar props = result.test_scripts.props\n\tprops.pending = totals.pending\n\tprops.failures = totals.failing_tests\n\tprops.passing = totals.passing_tests\n\tprops.tests = totals.tests\n\tprops.errors = gut.logger.get_errors().size()\n\tprops.warnings = gut.logger.get_warnings().size()\n\tprops.time =  gut.get_elapsed_time()\n\tprops.orphans = gut.get_orphan_counter().get_count()\n\tprops.risky = totals.risky\n\n\tresult.test_scripts.scripts = scripts\n\n\treturn result\n\n\nfunc write_json_file(gut, path):\n\tvar dict = get_results_dictionary(gut)\n\tvar json_text = JSON.stringify(dict, ' ')\n\n\tvar f_result = GutUtils.write_file(path, json_text)\n\tif(f_result != OK):\n\t\tvar msg = str(\"Error:  \", f_result, \".  Could not create export file \", path)\n\t\tGutUtils.get_logger().error(msg)\n\n\treturn f_result\n\n\n\nfunc write_summary_file(gut, path):\n\tvar dict = get_results_dictionary(gut, false)\n\tvar json_text = JSON.stringify(dict, ' ')\n\n\tvar f_result = GutUtils.write_file(path, json_text)\n\tif(f_result != OK):\n\t\tvar msg = str(\"Error:  \", f_result, \".  Could not create export file \", path)\n\t\tGutUtils.get_logger().error(msg)\n\n\treturn f_result\n"
  },
  {
    "path": "demo/addons/gut/result_exporter.gd.uid",
    "content": "uid://bbto5glvw8efv\n"
  },
  {
    "path": "demo/addons/gut/script_parser.gd",
    "content": "# These methods didn't have flags that would exclude them from being used\n# in a double and they appear to break things if they are included.\nconst BLACKLIST = [\n\t'get_script',\n\t'has_method',\n]\n\n\n# ------------------------------------------------------------------------------\n# Combins the meta for the method with additional information.\n# * flag for whether the method is local\n# * adds a 'default' property to all parameters that can be easily checked per\n#   parameter\n# ------------------------------------------------------------------------------\nclass ParsedMethod:\n\tconst NO_DEFAULT = '__no__default__'\n\n\tvar _meta = {}\n\tvar meta = _meta :\n\t\tget: return _meta\n\t\tset(val): return;\n\n\tvar is_local = false\n\tvar _parameters = []\n\n\tfunc _init(metadata):\n\t\t_meta = metadata\n\t\tvar start_default = _meta.args.size() - _meta.default_args.size()\n\t\tfor i in range(_meta.args.size()):\n\t\t\tvar arg = _meta.args[i]\n\t\t\t# Add a \"default\" property to the metadata so we don't have to do\n\t\t\t# weird default paramter position math again.\n\t\t\tif(i >= start_default):\n\t\t\t\targ['default'] = _meta.default_args[start_default - i]\n\t\t\telse:\n\t\t\t\targ['default'] = NO_DEFAULT\n\t\t\t_parameters.append(arg)\n\n\n\tfunc is_eligible_for_doubling():\n\t\tvar has_bad_flag = _meta.flags & \\\n\t\t\t(METHOD_FLAG_OBJECT_CORE | METHOD_FLAG_VIRTUAL | METHOD_FLAG_STATIC)\n\t\treturn !has_bad_flag and BLACKLIST.find(_meta.name) == -1\n\n\n\tfunc is_accessor():\n\t\treturn _meta.name.begins_with('@') and \\\n\t\t\t(_meta.name.ends_with('_getter') or _meta.name.ends_with('_setter'))\n\n\n\tfunc to_s():\n\t\tvar s = _meta.name + \"(\"\n\n\t\tfor i in range(_meta.args.size()):\n\t\t\tvar arg = _meta.args[i]\n\t\t\tif(str(arg.default) != NO_DEFAULT):\n\t\t\t\tvar val = str(arg.default)\n\t\t\t\tif(val == ''):\n\t\t\t\t\tval = '\"\"'\n\t\t\t\ts += str(arg.name, ' = ', val)\n\t\t\telse:\n\t\t\t\ts += str(arg.name)\n\n\t\t\tif(i != _meta.args.size() -1):\n\t\t\t\ts += ', '\n\n\t\ts += \")\"\n\t\treturn s\n\n\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nclass ParsedScript:\n\t# All methods indexed by name.\n\tvar _methods_by_name = {}\n\n\tvar _script_path = null\n\tvar script_path = _script_path :\n\t\tget: return _script_path\n\t\tset(val): return;\n\n\tvar _subpath = null\n\tvar subpath = null :\n\t\tget: return _subpath\n\t\tset(val): return;\n\n\tvar _resource = null\n\tvar resource = null :\n\t\tget: return _resource\n\t\tset(val): return;\n\n\n\tvar _is_native = false\n\tvar is_native = _is_native:\n\t\tget: return _is_native\n\t\tset(val): return;\n\n\tvar _native_methods = {}\n\tvar _native_class_name = \"\"\n\n\n\n\tfunc _init(script_or_inst, inner_class=null):\n\t\tvar to_load = script_or_inst\n\n\t\tif(GutUtils.is_native_class(to_load)):\n\t\t\t_resource = to_load\n\t\t\t_is_native = true\n\t\t\tvar inst = to_load.new()\n\t\t\t_native_class_name = inst.get_class()\n\t\t\t_native_methods = inst.get_method_list()\n\t\t\tif(!inst is RefCounted):\n\t\t\t\tinst.free()\n\t\telse:\n\t\t\tif(!script_or_inst is Resource):\n\t\t\t\tto_load = load(script_or_inst.get_script().get_path())\n\n\t\t\t_script_path = to_load.resource_path\n\t\t\tif(inner_class != null):\n\t\t\t\t_subpath = _find_subpath(to_load, inner_class)\n\n\t\t\tif(inner_class == null):\n\t\t\t\t_resource = to_load\n\t\t\telse:\n\t\t\t\t_resource = inner_class\n\t\t\t\tto_load = inner_class\n\n\t\t_parse_methods(to_load)\n\n\n\tfunc _print_flags(meta):\n\t\tprint(str(meta.name, ':').rpad(30), str(meta.flags).rpad(4), ' = ', GutUtils.dec2bistr(meta.flags, 10))\n\n\n\tfunc _get_native_methods(base_type):\n\t\tvar to_return = []\n\t\tif(base_type != null):\n\t\t\tvar source = str('extends ', base_type)\n\t\t\tvar inst = GutUtils.create_script_from_source(source).new()\n\t\t\tto_return = inst.get_method_list()\n\t\t\tif(! inst is RefCounted):\n\t\t\t\tinst.free()\n\t\treturn to_return\n\n\n\tfunc _parse_methods(thing):\n\t\tvar methods = []\n\t\tif(is_native):\n\t\t\tmethods = _native_methods.duplicate()\n\t\telse:\n\t\t\tvar base_type = thing.get_instance_base_type()\n\t\t\tmethods = _get_native_methods(base_type)\n\n\t\tfor m in methods:\n\t\t\tvar parsed = ParsedMethod.new(m)\n\t\t\t_methods_by_name[m.name] = parsed\n\t\t\t# _init must always be included so that we can initialize\n\t\t\t# double_tools\n\t\t\tif(m.name == '_init'):\n\t\t\t\tparsed.is_local = true\n\n\n\t\t# This loop will overwrite all entries in _methods_by_name with the local\n\t\t# method object so there is only ever one listing for a function with\n\t\t# the right \"is_local\" flag.\n\t\tif(!is_native):\n\t\t\tmethods = thing.get_script_method_list()\n\t\t\tfor m in methods:\n\t\t\t\tvar parsed_method = ParsedMethod.new(m)\n\t\t\t\tparsed_method.is_local = true\n\t\t\t\t_methods_by_name[m.name] = parsed_method\n\n\n\tfunc _find_subpath(parent_script, inner):\n\t\tvar const_map = parent_script.get_script_constant_map()\n\t\tvar consts = const_map.keys()\n\t\tvar const_idx = 0\n\t\tvar found = false\n\t\tvar to_return = null\n\n\t\twhile(const_idx < consts.size() and !found):\n\t\t\tvar key = consts[const_idx]\n\t\t\tvar const_val = const_map[key]\n\t\t\tif(typeof(const_val) == TYPE_OBJECT):\n\t\t\t\tif(const_val == inner):\n\t\t\t\t\tfound = true\n\t\t\t\t\tto_return = key\n\t\t\t\telse:\n\t\t\t\t\tto_return = _find_subpath(const_val, inner)\n\t\t\t\t\tif(to_return != null):\n\t\t\t\t\t\tto_return = str(key, '.', to_return)\n\t\t\t\t\t\tfound = true\n\n\t\t\tconst_idx += 1\n\n\t\treturn to_return\n\n\n\tfunc get_method(name):\n\t\treturn _methods_by_name[name]\n\n\n\tfunc get_super_method(name):\n\t\tvar to_return = get_method(name)\n\t\tif(to_return.is_local):\n\t\t\tto_return = null\n\n\t\treturn to_return\n\n\tfunc get_local_method(name):\n\t\tvar to_return = get_method(name)\n\t\tif(!to_return.is_local):\n\t\t\tto_return = null\n\n\t\treturn to_return\n\n\n\tfunc get_sorted_method_names():\n\t\tvar keys = _methods_by_name.keys()\n\t\tkeys.sort()\n\t\treturn keys\n\n\n\tfunc get_local_method_names():\n\t\tvar names = []\n\t\tfor method in _methods_by_name:\n\t\t\tif(_methods_by_name[method].is_local):\n\t\t\t\tnames.append(method)\n\n\t\treturn names\n\n\n\tfunc get_super_method_names():\n\t\tvar names = []\n\t\tfor method in _methods_by_name:\n\t\t\tif(!_methods_by_name[method].is_local):\n\t\t\t\tnames.append(method)\n\n\t\treturn names\n\n\n\tfunc get_local_methods():\n\t\tvar to_return = []\n\t\tfor key in _methods_by_name:\n\t\t\tvar method = _methods_by_name[key]\n\t\t\tif(method.is_local):\n\t\t\t\tto_return.append(method)\n\t\treturn to_return\n\n\n\tfunc get_super_methods():\n\t\tvar to_return = []\n\t\tfor key in _methods_by_name:\n\t\t\tvar method = _methods_by_name[key]\n\t\t\tif(!method.is_local):\n\t\t\t\tto_return.append(method)\n\t\treturn to_return\n\n\n\tfunc get_extends_text():\n\t\tvar text = null\n\t\tif(is_native):\n\t\t\ttext = str(\"extends \", _native_class_name)\n\t\telse:\n\t\t\ttext = str(\"extends '\", _script_path, \"'\")\n\t\t\tif(_subpath != null):\n\t\t\t\ttext += '.' + _subpath\n\t\treturn text\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nvar scripts = {}\n\nfunc _get_instance_id(thing):\n\tvar inst_id = null\n\n\tif(GutUtils.is_native_class(thing)):\n\t\tvar id_str = str(thing).replace(\"<\", '').replace(\">\", '').split('#')[1]\n\t\tinst_id = id_str.to_int()\n\telif(typeof(thing) == TYPE_STRING):\n\t\tif(FileAccess.file_exists(thing)):\n\t\t\tinst_id = load(thing).get_instance_id()\n\telse:\n\t\tinst_id = thing.get_instance_id()\n\n\treturn inst_id\n\n\nfunc parse(thing, inner_thing=null):\n\tvar key = -1\n\tif(inner_thing == null):\n\t\tkey = _get_instance_id(thing)\n\telse:\n\t\tkey = _get_instance_id(inner_thing)\n\n\tvar parsed = null\n\n\tif(key != null):\n\t\tif(scripts.has(key)):\n\t\t\tparsed = scripts[key]\n\t\telse:\n\t\t\tvar obj = instance_from_id(_get_instance_id(thing))\n\t\t\tvar inner = null\n\t\t\tif(inner_thing != null):\n\t\t\t\tinner = instance_from_id(_get_instance_id(inner_thing))\n\n\t\t\tif(obj is Resource or GutUtils.is_native_class(obj)):\n\t\t\t\tparsed = ParsedScript.new(obj, inner)\n\t\t\t\tscripts[key] = parsed\n\n\treturn parsed\n\n"
  },
  {
    "path": "demo/addons/gut/script_parser.gd.uid",
    "content": "uid://c4k82nmegjoec\n"
  },
  {
    "path": "demo/addons/gut/signal_watcher.gd",
    "content": "# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n\n# Some arbitrary string that should never show up by accident.  If it does, then\n# shame on  you.\nconst ARG_NOT_SET = '_*_argument_*_is_*_not_set_*_'\n\n# This hash holds the objects that are being watched, the signals that are being\n# watched, and an array of arrays that contains arguments that were passed\n# each time the signal was emitted.\n#\n# For example:\n#\t_watched_signals => {\n#\t\tref1 => {\n#\t\t\t'signal1' => [[], [], []],\n#\t\t\t'signal2' => [[p1, p2]],\n#\t\t\t'signal3' => [[p1]]\n#\t\t},\n#\t\tref2 => {\n#\t\t\t'some_signal' => [],\n#\t\t\t'other_signal' => [[p1, p2, p3], [p1, p2, p3], [p1, p2, p3]]\n#\t\t}\n#\t}\n#\n# In this sample:\n#\t- signal1 on the ref1 object was emitted 3 times and each time, zero\n#\tparameters were passed.\n#\t- signal3 on ref1 was emitted once and passed a single parameter\n#\t- some_signal on ref2 was never emitted.\n#\t- other_signal on ref2 was emitted 3 times, each time with 3 parameters.\nvar _watched_signals = {}\nvar _lgr = GutUtils.get_logger()\n\nfunc _add_watched_signal(obj, name):\n\t# SHORTCIRCUIT - ignore dupes\n\tif(_watched_signals.has(obj) and _watched_signals[obj].has(name)):\n\t\treturn\n\n\tif(!_watched_signals.has(obj)):\n\t\t_watched_signals[obj] = {name:[]}\n\telse:\n\t\t_watched_signals[obj][name] = []\n\tobj.connect(name,Callable(self,'_on_watched_signal').bind(obj,name))\n\n# This handles all the signals that are watched.  It supports up to 9 parameters\n# which could be emitted by the signal and the two parameters used when it is\n# connected via watch_signal.  I chose 9 since you can only specify up to 9\n# parameters when dynamically calling a method via call (per the Godot\n# documentation, i.e. some_object.call('some_method', 1, 2, 3...)).\n#\n# Based on the documentation of emit_signal, it appears you can only pass up\n# to 4 parameters when firing a signal.  I haven't verified this, but this should\n# future proof this some if the value ever grows.\nfunc _on_watched_signal(arg1=ARG_NOT_SET, arg2=ARG_NOT_SET, arg3=ARG_NOT_SET, \\\n\t\t\t\t\t\targ4=ARG_NOT_SET, arg5=ARG_NOT_SET, arg6=ARG_NOT_SET, \\\n\t\t\t\t\t\targ7=ARG_NOT_SET, arg8=ARG_NOT_SET, arg9=ARG_NOT_SET, \\\n\t\t\t\t\t\targ10=ARG_NOT_SET, arg11=ARG_NOT_SET):\n\tvar args = [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11]\n\n\t# strip off any unused vars.\n\tvar idx = args.size() -1\n\twhile(str(args[idx]) == ARG_NOT_SET):\n\t\targs.remove_at(idx)\n\t\tidx -= 1\n\n\t# retrieve object and signal name from the array and remove_at them.  These\n\t# will always be at the end since they are added when the connect happens.\n\tvar signal_name = args[args.size() -1]\n\targs.pop_back()\n\tvar object = args[args.size() -1]\n\targs.pop_back()\n\n\tif(_watched_signals.has(object)):\n\t\t_watched_signals[object][signal_name].append(args)\n\telse:\n\t\t_lgr.error(str(\"signal_watcher._on_watched_signal:  Got signal for unwatched object:  \", object, '::', signal_name))\n\n# This parameter stuff should go into test.gd not here.  This thing works\n# just fine the way it is.\nfunc _obj_name_pair(obj_or_signal, signal_name=null):\n\tvar to_return = {\n\t\t'object' : obj_or_signal,\n\t\t'signal_name' : signal_name\n\t}\n\tif(obj_or_signal is Signal):\n\t\tto_return.object =  obj_or_signal.get_object()\n\t\tto_return.signal_name = obj_or_signal.get_name()\n\n\treturn to_return\n\n\nfunc does_object_have_signal(object, signal_name):\n\tvar signals = object.get_signal_list()\n\tfor i in range(signals.size()):\n\t\tif(signals[i]['name'] == signal_name):\n\t\t\treturn true\n\treturn false\n\nfunc watch_signals(object):\n\tvar signals = object.get_signal_list()\n\tfor i in range(signals.size()):\n\t\t_add_watched_signal(object, signals[i]['name'])\n\nfunc watch_signal(object, signal_name):\n\tvar did = false\n\tif(does_object_have_signal(object, signal_name)):\n\t\t_add_watched_signal(object, signal_name)\n\t\tdid = true\n\telse:\n\t\tGutUtils.get_logger().warn(str(object, ' does not have signal ', signal_name))\n\treturn did\n\nfunc get_emit_count(object, signal_name):\n\tvar to_return = -1\n\tif(is_watching(object, signal_name)):\n\t\tto_return = _watched_signals[object][signal_name].size()\n\treturn to_return\n\nfunc did_emit(object, signal_name=null):\n\tvar vals = _obj_name_pair(object, signal_name)\n\tvar did = false\n\tif(is_watching(vals.object, vals.signal_name)):\n\t\tdid = get_emit_count(vals.object, vals.signal_name) != 0\n\treturn did\n\nfunc print_object_signals(object):\n\tvar list = object.get_signal_list()\n\tfor i in range(list.size()):\n\t\tprint(list[i].name, \"\\n  \", list[i])\n\nfunc get_signal_parameters(object, signal_name, index=-1):\n\tvar params = null\n\tif(is_watching(object, signal_name)):\n\t\tvar all_params = _watched_signals[object][signal_name]\n\t\tif(all_params.size() > 0):\n\t\t\tif(index == -1):\n\t\t\t\tindex = all_params.size() -1\n\t\t\tparams = all_params[index]\n\treturn params\n\nfunc is_watching_object(object):\n\treturn _watched_signals.has(object)\n\nfunc is_watching(object, signal_name):\n\treturn _watched_signals.has(object) and _watched_signals[object].has(signal_name)\n\nfunc clear():\n\tfor obj in _watched_signals:\n\t\tif(GutUtils.is_not_freed(obj)):\n\t\t\tfor signal_name in _watched_signals[obj]:\n\t\t\t\tobj.disconnect(signal_name, Callable(self,'_on_watched_signal'))\n\t_watched_signals.clear()\n\n# Returns a list of all the signal names that were emitted by the object.\n# If the object is not being watched then an empty list is returned.\nfunc get_signals_emitted(obj):\n\tvar emitted = []\n\tif(is_watching_object(obj)):\n\t\tfor signal_name in _watched_signals[obj]:\n\t\t\tif(_watched_signals[obj][signal_name].size() > 0):\n\t\t\t\temitted.append(signal_name)\n\n\treturn emitted\n\n\nfunc get_signal_summary(obj):\n\tvar emitted = {}\n\tif(is_watching_object(obj)):\n\t\tfor signal_name in _watched_signals[obj]:\n\t\t\tif(_watched_signals[obj][signal_name].size() > 0):\n\t\t\t\t# maybe this could return parameters if any were sent.  should\n\t\t\t\t# have an empty list if no parameters were ever sent to the\n\t\t\t\t# signal.  Or this all just gets moved into print_signal_summary\n\t\t\t\t# since this wouldn't be that useful without more data in the\n\t\t\t\t# summary.\n\t\t\t\tvar entry = {\n\t\t\t\t\temit_count = get_emit_count(obj, signal_name)\n\t\t\t\t}\n\t\t\t\temitted[signal_name] = entry\n\n\treturn emitted\n\n\nfunc print_signal_summary(obj):\n\tif(!is_watching_object(obj)):\n\t\tvar msg = str('Not watching signals for ', obj)\n\t\tGutUtils.get_logger().warn(msg)\n\t\treturn\n\n\tvar summary = get_signal_summary(obj)\n\tprint(obj, '::Signals')\n\tvar sorted = summary.keys()\n\tsorted.sort()\n\tfor key in sorted:\n\t\tprint(' -  ', key, ' x ', summary[key].emit_count)\n"
  },
  {
    "path": "demo/addons/gut/signal_watcher.gd.uid",
    "content": "uid://yj7vo3wcr68q\n"
  },
  {
    "path": "demo/addons/gut/spy.gd",
    "content": "# {\n#   instance_id_or_path1:{\n#       method1:[ [p1, p2], [p1, p2] ],\n#       method2:[ [p1, p2], [p1, p2] ]\n#   },\n#   instance_id_or_path1:{\n#       method1:[ [p1, p2], [p1, p2] ],\n#       method2:[ [p1, p2], [p1, p2] ]\n#   },\n# }\nvar _calls = {}\nvar _lgr = GutUtils.get_logger()\nvar _compare = GutUtils.Comparator.new()\n\nfunc _find_parameters(call_params, params_to_find):\n\tvar found = false\n\tvar idx = 0\n\twhile(idx < call_params.size() and !found):\n\t\tvar result = _compare.deep(call_params[idx], params_to_find)\n\t\tif(result.are_equal):\n\t\t\tfound = true\n\t\telse:\n\t\t\tidx += 1\n\treturn found\n\n\nfunc _get_params_as_string(params):\n\tvar to_return = ''\n\tif(params == null):\n\t\treturn ''\n\n\tfor i in range(params.size()):\n\t\tif(params[i] == null):\n\t\t\tto_return += 'null'\n\t\telse:\n\t\t\tif(typeof(params[i]) == TYPE_STRING):\n\t\t\t\tto_return += str('\"', params[i], '\"')\n\t\t\telse:\n\t\t\t\tto_return += str(params[i])\n\t\tif(i != params.size() -1):\n\t\t\tto_return += ', '\n\treturn to_return\n\n\nfunc add_call(variant, method_name, parameters=null):\n\tif(!_calls.has(variant)):\n\t\t_calls[variant] = {}\n\n\tif(!_calls[variant].has(method_name)):\n\t\t_calls[variant][method_name] = []\n\n\t_calls[variant][method_name].append(parameters)\n\n\nfunc was_called(variant, method_name, parameters=null):\n\tvar to_return = false\n\tif(_calls.has(variant) and _calls[variant].has(method_name)):\n\t\tif(parameters):\n\t\t\tto_return = _find_parameters(_calls[variant][method_name], parameters)\n\t\telse:\n\t\t\tto_return = true\n\treturn to_return\n\n\nfunc get_call_parameters(variant, method_name, index=-1):\n\tvar to_return = null\n\tvar get_index = -1\n\n\tif(_calls.has(variant) and _calls[variant].has(method_name)):\n\t\tvar call_size = _calls[variant][method_name].size()\n\t\tif(index == -1):\n\t\t\t# get the most recent call by default\n\t\t\tget_index =  call_size -1\n\t\telse:\n\t\t\tget_index = index\n\n\t\tif(get_index < call_size):\n\t\t\tto_return = _calls[variant][method_name][get_index]\n\t\telse:\n\t\t\t_lgr.error(str('Specified index ', index, ' is outside range of the number of registered calls:  ', call_size))\n\n\treturn to_return\n\n\nfunc call_count(instance, method_name, parameters=null):\n\tvar to_return = 0\n\n\tif(was_called(instance, method_name)):\n\t\tif(parameters):\n\t\t\tfor i in range(_calls[instance][method_name].size()):\n\t\t\t\tif(_calls[instance][method_name][i] == parameters):\n\t\t\t\t\tto_return += 1\n\t\telse:\n\t\t\tto_return = _calls[instance][method_name].size()\n\treturn to_return\n\n\nfunc clear():\n\t_calls = {}\n\n\nfunc get_call_list_as_string(instance):\n\tvar to_return = ''\n\tif(_calls.has(instance)):\n\t\tfor method in _calls[instance]:\n\t\t\tfor i in range(_calls[instance][method].size()):\n\t\t\t\tto_return += str(method, '(', _get_params_as_string(_calls[instance][method][i]), \")\\n\")\n\treturn to_return\n\n\nfunc get_logger():\n\treturn _lgr\n\n\nfunc set_logger(logger):\n\t_lgr = logger\n"
  },
  {
    "path": "demo/addons/gut/spy.gd.uid",
    "content": "uid://blj7je6n53r51\n"
  },
  {
    "path": "demo/addons/gut/strutils.gd",
    "content": "class_name GutStringUtils\n\n# Hash containing all the built in types in Godot.  This provides an English\n# name for the types that corosponds with the type constants defined in the\n# engine.\nvar types = {}\n\nfunc _init_types_dictionary():\n\ttypes[TYPE_NIL] = 'NIL'\n\ttypes[TYPE_AABB] = 'AABB'\n\ttypes[TYPE_ARRAY] = 'ARRAY'\n\ttypes[TYPE_BASIS] = 'BASIS'\n\ttypes[TYPE_BOOL] = 'BOOL'\n\ttypes[TYPE_CALLABLE] = 'CALLABLE'\n\ttypes[TYPE_COLOR] = 'COLOR'\n\ttypes[TYPE_DICTIONARY] = 'DICTIONARY'\n\ttypes[TYPE_FLOAT] = 'FLOAT'\n\ttypes[TYPE_INT] = 'INT'\n\ttypes[TYPE_MAX] = 'MAX'\n\ttypes[TYPE_NODE_PATH] = 'NODE_PATH'\n\ttypes[TYPE_OBJECT] = 'OBJECT'\n\ttypes[TYPE_PACKED_BYTE_ARRAY] = 'PACKED_BYTE_ARRAY'\n\ttypes[TYPE_PACKED_COLOR_ARRAY] = 'PACKED_COLOR_ARRAY'\n\ttypes[TYPE_PACKED_FLOAT32_ARRAY] = 'PACKED_FLOAT32_ARRAY'\n\ttypes[TYPE_PACKED_FLOAT64_ARRAY] = 'PACKED_FLOAT64_ARRAY'\n\ttypes[TYPE_PACKED_INT32_ARRAY] = 'PACKED_INT32_ARRAY'\n\ttypes[TYPE_PACKED_INT64_ARRAY] = 'PACKED_INT64_ARRAY'\n\ttypes[TYPE_PACKED_STRING_ARRAY] = 'PACKED_STRING_ARRAY'\n\ttypes[TYPE_PACKED_VECTOR2_ARRAY] = 'PACKED_VECTOR2_ARRAY'\n\ttypes[TYPE_PACKED_VECTOR3_ARRAY] = 'PACKED_VECTOR3_ARRAY'\n\ttypes[TYPE_PLANE] = 'PLANE'\n\ttypes[TYPE_PROJECTION] = 'PROJECTION'\n\ttypes[TYPE_QUATERNION] = 'QUATERNION'\n\ttypes[TYPE_RECT2] = 'RECT2'\n\ttypes[TYPE_RECT2I] = 'RECT2I'\n\ttypes[TYPE_RID] = 'RID'\n\ttypes[TYPE_SIGNAL] = 'SIGNAL'\n\ttypes[TYPE_STRING_NAME] = 'STRING_NAME'\n\ttypes[TYPE_STRING] = 'STRING'\n\ttypes[TYPE_TRANSFORM2D] = 'TRANSFORM2D'\n\ttypes[TYPE_TRANSFORM3D] = 'TRANSFORM3D'\n\ttypes[TYPE_VECTOR2] = 'VECTOR2'\n\ttypes[TYPE_VECTOR2I] = 'VECTOR2I'\n\ttypes[TYPE_VECTOR3] = 'VECTOR3'\n\ttypes[TYPE_VECTOR3I] = 'VECTOR3I'\n\ttypes[TYPE_VECTOR4] = 'VECTOR4'\n\ttypes[TYPE_VECTOR4I] = 'VECTOR4I'\n\n# Types to not be formatted when using _str\nvar _str_ignore_types = [\n\tTYPE_INT, TYPE_FLOAT, TYPE_STRING,\n\tTYPE_NIL, TYPE_BOOL\n]\n\nfunc _init():\n\t_init_types_dictionary()\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nfunc _get_filename(path):\n\treturn path.split('/')[-1]\n\n# ------------------------------------------------------------------------------\n# Gets the filename of an object passed in.  This does not return the\n# full path to the object, just the filename.\n# ------------------------------------------------------------------------------\nfunc _get_obj_filename(thing):\n\tvar filename = null\n\n\tif(thing == null or\n\t\tGutUtils.is_native_class(thing) or\n\t\t!is_instance_valid(thing) or\n\t\tstr(thing) == '<Object#null>' or\n\t\ttypeof(thing) != TYPE_OBJECT or\n\t\tGutUtils.is_double(thing)):\n\t\treturn\n\n\tif(thing.get_script() == null):\n\t\tif(thing is PackedScene):\n\t\t\tfilename = _get_filename(thing.resource_path)\n\t\telse:\n\t\t\t# If it isn't a packed scene and it doesn't have a script then\n\t\t\t# we do nothing.  This just reads better.\n\t\t\tpass\n\telif(!GutUtils.is_native_class(thing)):\n\t\tvar dict = inst_to_dict(thing)\n\t\tfilename = _get_filename(dict['@path'])\n\t\tif(str(dict['@subpath']) != ''):\n\t\t\tfilename += str('/', dict['@subpath'])\n\n\treturn filename\n\n# ------------------------------------------------------------------------------\n# Better object/thing to string conversion.  Includes extra details about\n# whatever is passed in when it can/should.\n# ------------------------------------------------------------------------------\nfunc type2str(thing):\n\tvar filename = _get_obj_filename(thing)\n\tvar str_thing = str(thing)\n\n\tif(thing == null):\n\t\t# According to str there is a difference between null and an Object\n\t\t# that is somehow null.  To avoid getting '[Object:null]' as output\n\t\t# always set it to str(null) instead of str(thing).  A null object\n\t\t# will pass typeof(thing) == TYPE_OBJECT check so this has to be\n\t\t# before that.\n\t\tstr_thing = str(null)\n\telif(typeof(thing) == TYPE_FLOAT):\n\t\tif(!'.' in str_thing):\n\t\t\tstr_thing += '.0'\n\telif(typeof(thing) == TYPE_STRING):\n\t\tstr_thing = str('\"', thing, '\"')\n\telif(typeof(thing) in _str_ignore_types):\n\t\t# do nothing b/c we already have str(thing) in\n\t\t# to_return.  I think this just reads a little\n\t\t# better this way.\n\t\tpass\n\telif(typeof(thing) == TYPE_OBJECT):\n\t\tif(GutUtils.is_native_class(thing)):\n\t\t\tstr_thing = GutUtils.get_native_class_name(thing)\n\t\telif(GutUtils.is_double(thing)):\n\t\t\tvar double_path = _get_filename(thing.__gutdbl.thepath)\n\t\t\tif(thing.__gutdbl.subpath != ''):\n\t\t\t\tdouble_path += str('/', thing.__gutdbl.subpath)\n\t\t\telif(thing.__gutdbl.from_singleton != ''):\n\t\t\t\tdouble_path = thing.__gutdbl.from_singleton + \" Singleton\"\n\n\t\t\tvar double_type = \"double\"\n\t\t\tif(thing.__gutdbl.is_partial):\n\t\t\t\tdouble_type = \"partial-double\"\n\n\t\t\tstr_thing += str(\"(\", double_type, \" of \", double_path, \")\")\n\n\t\t\tfilename = null\n\telif(types.has(typeof(thing))):\n\t\tif(!str_thing.begins_with('(')):\n\t\t\tstr_thing = '(' + str_thing + ')'\n\t\tstr_thing = str(types[typeof(thing)], str_thing)\n\n\tif(filename != null):\n\t\tstr_thing += str('(', filename, ')')\n\treturn str_thing\n\n# ------------------------------------------------------------------------------\n# Returns the string truncated with an '...' in it.  Shows the start and last\n# 10 chars.  If the string is  smaller than max_size the entire string is\n# returned.  If max_size is -1 then truncation is skipped.\n# ------------------------------------------------------------------------------\nfunc truncate_string(src, max_size):\n\tvar to_return = src\n\tif(src.length() > max_size - 10 and max_size != -1):\n\t\tto_return = str(src.substr(0, max_size - 10), '...',  src.substr(src.length() - 10, src.length()))\n\treturn to_return\n\n\nfunc _get_indent_text(times, pad):\n\tvar to_return = ''\n\tfor i in range(times):\n\t\tto_return += pad\n\n\treturn to_return\n\nfunc indent_text(text, times, pad):\n\tif(times == 0):\n\t\treturn text\n\n\tvar to_return = text\n\tvar ending_newline = ''\n\n\tif(text.ends_with(\"\\n\")):\n\t\tending_newline = \"\\n\"\n\t\tto_return = to_return.left(to_return.length() -1)\n\n\tvar padding = _get_indent_text(times, pad)\n\tto_return = to_return.replace(\"\\n\", \"\\n\" + padding)\n\tto_return += ending_newline\n\n\treturn padding + to_return\n"
  },
  {
    "path": "demo/addons/gut/strutils.gd.uid",
    "content": "uid://dntjtiq2ppvmq\n"
  },
  {
    "path": "demo/addons/gut/stub_params.gd",
    "content": "var _is_return_override = false\nvar _is_defaults_override = false\nvar _is_call_override = false\nvar _method_meta : Dictionary = {}\n\n\nvar _lgr = GutUtils.get_logger()\nvar logger = _lgr :\n\tget: return _lgr\n\tset(val): _lgr = val\n\nvar return_val = null\nvar stub_target = null\nvar parameters = null # the parameter values to match method call on.\nvar stub_method = null\nvar call_super = false\nvar call_this = null\n\n# Whether this is a stub for default parameter values as they are defined in\n# the script, and not an overridden default value.\nvar is_script_default = false\n\nvar parameter_count = -1 :\n\tget():\n\t\t_lgr.deprecated(\"parameter count deprecated\")\n\t\treturn -1\n\n# Default values for parameters.  This is used to store default values for\n# scripts and to override those values.  I'm not sure if there is a need to\n# override them anymore, since I think this was introduced for stubbing vararg\n# methods, but you still can for now.  This value should only be used if\n# is_defaults_override is true.\nvar parameter_defaults = []\n\nconst NOT_SET = '|_1_this_is_not_set_1_|'\n\nfunc _init(target=null, method=null, _subpath=null):\n\tstub_target = target\n\tstub_method = method\n\n\tif(typeof(target) == TYPE_CALLABLE):\n\t\tstub_target = target.get_object()\n\t\tstub_method = target.get_method()\n\t\tparameters = target.get_bound_arguments()\n\t\tif(parameters.size() == 0):\n\t\t\tparameters = null\n\telif(typeof(target) == TYPE_STRING):\n\t\tif(target.is_absolute_path()):\n\t\t\tstub_target = load(str(target))\n\t\telse:\n\t\t\t_lgr.warn(str(target, ' is not a valid path'))\n\n\tif(stub_target is PackedScene):\n\t\tstub_target = GutUtils.get_scene_script_object(stub_target)\n\n\t# this is used internally to stub default parameters for everything that is\n\t# doubled...or something.  Look for stub_defaults_from_meta for usage.  This\n\t# behavior is not to be used by end users.\n\tif(typeof(method) == TYPE_DICTIONARY):\n\t\t_method_meta = method\n\t\t_load_defaults_from_metadata(method)\n\t\tis_script_default = true\n\n\nfunc _load_defaults_from_metadata(meta):\n\tstub_method = meta.name\n\tvar values = meta.default_args.duplicate()\n\twhile (values.size() < meta.args.size()):\n\t\tvalues.push_front(null)\n\n\tparam_defaults(values)\n\n\nfunc _get_method_meta():\n\tif(_method_meta == {} and typeof(stub_target) == TYPE_OBJECT):\n\t\tvar found_meta = GutUtils.get_method_meta(stub_target, stub_method)\n\t\tif(found_meta != null):\n\t\t\t_method_meta = found_meta\n\treturn _method_meta\n\n\n# -------------------------\n# Public\n# -------------------------\nfunc to_return(val):\n\treturn_val = val\n\tcall_super = false\n\t_is_return_override = true\n\n\treturn self\n\n\nfunc to_do_nothing():\n\tto_return(null)\n\treturn self\n\n\nfunc to_call_super():\n\tcall_super = true\n\t_is_call_override = true\n\treturn self\n\n\nfunc to_call(callable : Callable):\n\tcall_this = callable\n\t_is_call_override = true\n\treturn self\n\n\nfunc when_passed(p1=NOT_SET,p2=NOT_SET,p3=NOT_SET,p4=NOT_SET,p5=NOT_SET,p6=NOT_SET,p7=NOT_SET,p8=NOT_SET,p9=NOT_SET,p10=NOT_SET):\n\tparameters = [p1,p2,p3,p4,p5,p6,p7,p8,p9,p10]\n\tvar idx = 0\n\twhile(idx < parameters.size()):\n\t\tif(str(parameters[idx]) == NOT_SET):\n\t\t\tparameters.remove_at(idx)\n\t\telse:\n\t\t\tidx += 1\n\treturn self\n\n\nfunc param_count(_x):\n\t_lgr.deprecated(\"Stubbing param_count is no longer required or supported.\")\n\treturn self\n\n\nfunc param_defaults(values):\n\tvar meta = _get_method_meta()\n\tif(meta != {} and meta.flags & METHOD_FLAG_VARARG):\n\t\t_lgr.error(\"Cannot stub defaults for methods with varargs.\")\n\telse:\n\t\tparameter_defaults = values\n\t\t_is_defaults_override = true\n\treturn self\n\n\nfunc is_default_override_only():\n\treturn is_defaults_override() and !is_return_override() and !is_call_override()\n\n\nfunc is_return_override():\n\treturn _is_return_override\n\n\nfunc is_defaults_override():\n\treturn _is_defaults_override\n\n\nfunc is_call_override():\n\treturn _is_call_override\n\n\nfunc to_s():\n\tvar base_string = str(stub_target, '.', stub_method)\n\n\tif(parameter_defaults.size() > 0):\n\t\tbase_string += str(\" defaults \", parameter_defaults)\n\n\tif(call_super):\n\t\tbase_string += \" to call SUPER\"\n\n\tif(call_this != null):\n\t\tbase_string += str(\" to call \", call_this)\n\n\tif(parameters != null):\n\t\tbase_string += str(' with params (', parameters, ') returns ', return_val)\n\telse:\n\t\tbase_string += str(' returns ', return_val)\n\n\treturn base_string\n"
  },
  {
    "path": "demo/addons/gut/stub_params.gd.uid",
    "content": "uid://dblktyc7hyt4f\n"
  },
  {
    "path": "demo/addons/gut/stubber.gd",
    "content": "\nstatic var _class_db_name_hash = {} :\n\tget():\n\t\tif(_class_db_name_hash == {}):\n\t\t\t_class_db_name_hash = _make_crazy_dynamic_over_engineered_class_db_hash()\n\t\treturn _class_db_name_hash\n\n\n# So, I couldn't figure out how to get to a reference for a GDNative Class\n# using a string.  ClassDB has all thier names...so I made a hash using those\n# names and the classes.  Then I dynmaically make a script that has that as\n# the source and grab the hash out of it and return it.  Super Rube Golbergery,\n# but tons of fun.\nstatic func _make_crazy_dynamic_over_engineered_class_db_hash():\n\tvar text = \"var all_the_classes: Dictionary = {\\n\"\n\tfor classname in ClassDB.get_class_list():\n\t\tif(ClassDB.can_instantiate(classname)):\n\t\t\ttext += str('\"', classname, '\": ', classname, \", \\n\")\n\t\telse:\n\t\t\ttext += str('# ', classname, \"\\n\")\n\ttext += \"}\"\n\tvar inst =  GutUtils.create_script_from_source(text).new()\n\treturn inst.all_the_classes\n\n\n# -------------\n# returns{} and parameters {} have the followin structure\n# -------------\n# {\n# \tinst_id_or_path1:{\n# \t\tmethod_name1: [StubParams, StubParams],\n# \t\tmethod_name2: [StubParams, StubParams]\n# \t},\n# \tinst_id_or_path2:{\n# \t\tmethod_name1: [StubParams, StubParams],\n# \t\tmethod_name2: [StubParams, StubParams]\n# \t}\n# }\nvar returns = {}\nvar _lgr = GutUtils.get_logger()\nvar _strutils = GutUtils.Strutils.new()\n\n\nfunc _find_matches(obj, method):\n\tvar matches = []\n\tvar last_not_null_parent = null\n\n\t# Search for what is passed in first.  This could be a class or an instance.\n\t# We want to find the instance before we find the class.  If we do not have\n\t# an entry for the instance then see if we have an entry for the class.\n\tif(returns.has(obj) and returns[obj].has(method)):\n\t\tmatches = returns[obj][method]\n\telif(GutUtils.is_instance(obj)):\n\t\tvar parent = obj.get_script()\n\t\tvar found = false\n\t\twhile(parent != null and !found):\n\t\t\tfound = returns.has(parent)\n\n\t\t\tif(!found):\n\t\t\t\tlast_not_null_parent = parent\n\t\t\t\tparent = parent.get_base_script()\n\n\t\t# Could not find the script so check to see if a native class of this\n\t\t# type was stubbed.\n\t\tif(!found):\n\t\t\tvar base_type = last_not_null_parent.get_instance_base_type()\n\t\t\tif(_class_db_name_hash.has(base_type)):\n\t\t\t\tparent = _class_db_name_hash[base_type]\n\t\t\t\tfound = returns.has(parent)\n\n\t\tif(found and returns[parent].has(method)):\n\t\t\tmatches = returns[parent][method]\n\n\treturn matches\n\n\n# Searches returns for an entry that matches the instance or the class that\n# passed in obj is.\n#\n# obj can be an instance, class, or a path.\nfunc _find_stub(obj, method, parameters=null, find_overloads=false):\n\tvar to_return = null\n\tvar matches = _find_matches(obj, method)\n\n\tif(matches.size() == 0):\n\t\treturn null\n\n\tvar param_match = null\n\tvar null_match = null\n\tvar overload_match = null\n\n\tfor i in range(matches.size()):\n\t\tvar cur_stub = matches[i]\n\t\tif(cur_stub.parameters == parameters):\n\t\t\tparam_match = cur_stub\n\n\t\tif(cur_stub.parameters == null and !cur_stub.is_default_override_only()):\n\t\t\tnull_match = cur_stub\n\n\t\tif(cur_stub.is_defaults_override):\n\t\t\tif(overload_match == null || overload_match.is_script_default):\n\t\t\t\toverload_match = cur_stub\n\n\tif(find_overloads and overload_match != null):\n\t\tto_return = overload_match\n\t# We have matching parameter values so return the stub value for that\n\telif(param_match != null):\n\t\tto_return = param_match\n\t# We found a case where the parameters were not specified so return\n\t# parameters for that.  Only do this if the null match is not *just*\n\t# a paramerter override stub.\n\telif(null_match != null):\n\t\tto_return = null_match\n\n\treturn to_return\n\n\n\n# ##############\n# Public\n# ##############\n\nfunc add_stub(stub_params):\n\tstub_params._lgr = _lgr\n\tvar key = stub_params.stub_target\n\n\tif(!returns.has(key)):\n\t\treturns[key] = {}\n\n\tif(!returns[key].has(stub_params.stub_method)):\n\t\treturns[key][stub_params.stub_method] = []\n\n\treturns[key][stub_params.stub_method].append(stub_params)\n\n\n# Gets a stubbed return value for the object and method passed in.  If the\n# instance was stubbed it will use that, otherwise it will use the path and\n# subpath of the object to try to find a value.\n#\n# It will also use the optional list of parameter values to find a value.  If\n# the object was stubbed with no parameters than any parameters will match.\n# If it was stubbed with specific parameter values then it will try to match.\n# If the parameters do not match BUT there was also an empty parameter list stub\n# then it will return those.\n# If it cannot find anything that matches then null is returned.for\n#\n# Parameters\n# obj:  this should be an instance of a doubled object.\n# method:  the method called\n# parameters:  optional array of parameter vales to find a return value for.\nfunc get_return(obj, method, parameters=null):\n\tvar stub_info = _find_stub(obj, method, parameters)\n\n\tif(stub_info != null):\n\t\treturn stub_info.return_val\n\telse:\n\t\t_lgr.info(str('Call to [', method, '] was not stubbed for the supplied parameters ', parameters, '.  Null was returned.'))\n\t\treturn null\n\n\nfunc should_call_super(obj, method, parameters=null):\n\tvar stub_info = _find_stub(obj, method, parameters)\n\n\tvar is_partial = false\n\tif(typeof(obj) != TYPE_STRING): # some stubber tests test with strings\n\t\tis_partial = obj.__gutdbl.is_partial\n\tvar should = is_partial\n\n\tif(stub_info != null):\n\t\tshould = stub_info.call_super\n\telif(!is_partial):\n\t\t# this log message is here because of how the generated doubled scripts\n\t\t# are structured.  With this log msg here, you will only see one\n\t\t# \"unstubbed\" info instead of multiple.\n\t\t_lgr.info('Unstubbed call to ' + method + '::' + _strutils.type2str(obj))\n\t\tshould = false\n\n\treturn should\n\n\nfunc get_call_this(obj, method, parameters=null):\n\tvar stub_info = _find_stub(obj, method, parameters)\n\n\tif(stub_info != null):\n\t\treturn stub_info.call_this\n\n\nfunc get_default_value(obj, method, p_index):\n\tvar matches = _find_matches(obj, method)\n\tvar the_defaults = []\n\tvar script_defaults = []\n\tvar i = matches.size() -1\n\n\twhile(i >= 0 and the_defaults.is_empty()):\n\t\tif(matches[i].is_defaults_override()):\n\t\t\tif(matches[i].is_script_default):\n\t\t\t\tscript_defaults = matches[i].parameter_defaults\n\t\t\telse:\n\t\t\t\tthe_defaults = matches[i].parameter_defaults\n\t\ti -= 1\n\n\tif(the_defaults.is_empty() and !script_defaults.is_empty()):\n\t\tthe_defaults = script_defaults\n\n\tvar to_return = null\n\tif(the_defaults.size() > p_index):\n\t\tto_return = the_defaults[p_index]\n\n\treturn to_return\n\n\nfunc clear():\n\treturns.clear()\n\n\nfunc get_logger():\n\treturn _lgr\n\n\nfunc set_logger(logger):\n\t_lgr = logger\n\n\nfunc to_s():\n\tvar text = ''\n\tfor thing in returns:\n\t\ttext += str(\"-- \", thing, \" --\\n\")\n\t\tfor method in returns[thing]:\n\t\t\ttext += str(\"\\t\", method, \"\\n\")\n\t\t\tfor i in range(returns[thing][method].size()):\n\t\t\t\ttext += \"\\t\\t\" + returns[thing][method][i].to_s() + \"\\n\"\n\n\tif(text == ''):\n\t\ttext = 'Stubber is empty';\n\n\treturn text\n\n\nfunc stub_defaults_from_meta(target, method_meta):\n\tvar params = GutUtils.StubParams.new(target, method_meta)\n\tparams.is_script_default = true\n\tadd_stub(params)\n"
  },
  {
    "path": "demo/addons/gut/stubber.gd.uid",
    "content": "uid://cjrpyjnk8lkq8\n"
  },
  {
    "path": "demo/addons/gut/summary.gd",
    "content": "# ------------------------------------------------------------------------------\n# Prints things, mostly.  Knows too much about gut.gd, but it's only supposed to\n# work with gut.gd, so I'm fine with that.\n# ------------------------------------------------------------------------------\n# a _test_collector to use when one is not provided.\nvar _gut = null\n\n\nfunc _init(gut=null):\n\t_gut = gut\n\n# ---------------------\n# Private\n# ---------------------\nfunc _log_end_run_header(gut):\n\tvar lgr = gut.get_logger()\n\tlgr.log('==============================================', lgr.fmts.yellow)\n\tlgr.log(\"= Run Summary\", lgr.fmts.yellow)\n\tlgr.log('==============================================', lgr.fmts.yellow)\n\n\nfunc _log_what_was_run(gut):\n\tif(!GutUtils.is_null_or_empty(gut._select_script)):\n\t\tgut.p('Ran Scripts matching \"' + gut._select_script + '\"')\n\tif(!GutUtils.is_null_or_empty(gut._unit_test_name)):\n\t\tgut.p('Ran Tests matching \"' + gut._unit_test_name + '\"')\n\tif(!GutUtils.is_null_or_empty(gut._inner_class_name)):\n\t\tgut.p('Ran Inner Classes matching \"' + gut._inner_class_name + '\"')\n\n\nfunc _total_fmt(text, value):\n\tvar space = 18\n\tif(str(value) == '0'):\n\t\tvalue = 'none'\n\treturn str(text.rpad(space), str(value).lpad(5))\n\n\nfunc _log_non_zero_total(text, value, lgr):\n\tif(str(value) != '0'):\n\t\tlgr.log(_total_fmt(text, value))\n\t\treturn 1\n\telse:\n\t\treturn 0\n\n\nfunc _log_totals(gut, totals):\n\tvar lgr = gut.get_logger()\n\tlgr.log()\n\n\t# lgr.log(\"---- Totals ----\")\n\tlgr.log(\"Totals\")\n\tlgr.log(\"------\")\n\tvar issue_count = 0\n\tissue_count += _log_non_zero_total('Errors', totals.errors, lgr)\n\tissue_count += _log_non_zero_total('Warnings', totals.warnings, lgr)\n\tissue_count += _log_non_zero_total('Deprecated', totals.deprecated, lgr)\n\tif(issue_count > 0):\n\t\tlgr.log(\"\")\n\n\tlgr.log(_total_fmt( 'Scripts', totals.scripts))\n\tlgr.log(_total_fmt( 'Tests', gut.get_test_collector().get_ran_test_count()))\n\tlgr.log(_total_fmt( 'Passing Tests', totals.passing_tests))\n\t_log_non_zero_total('Failing Tests', totals.failing_tests, lgr)\n\t_log_non_zero_total('Risky/Pending', totals.risky + totals.pending, lgr)\n\tif(totals.failing == 0):\n\t\tlgr.log(_total_fmt( 'Asserts', totals.passing + totals.failing))\n\telse:\n\t\tlgr.log(_total_fmt( 'Asserts', str(totals.passing, '/', totals.passing + totals.failing)))\n\t_log_non_zero_total( 'Orphans', totals.orphans, lgr)\n\tlgr.log(_total_fmt( 'Time', str(gut.get_elapsed_time(), 's')))\n\n\treturn totals\n\n\nfunc _log_nothing_run(gut):\n\tvar lgr = gut.get_logger()\n\tlgr.error(\"Nothing was run.\")\n\tlgr.log('On the one hand nothing failed, on the other hand nothing did anything.')\n\t_log_what_was_run(gut)\n\n\n# ---------------------\n# Public\n# ---------------------\nfunc log_all_non_passing_tests(gut=_gut):\n\tvar test_collector = gut.get_test_collector()\n\tvar lgr = gut.get_logger()\n\n\tvar to_return = {\n\t\tpassing = 0,\n\t\tnon_passing = 0\n\t}\n\n\tfor test_script in test_collector.scripts:\n\t\tlgr.set_indent_level(0)\n\n\t\tif(test_script.was_skipped or test_script.get_fail_count() > 0 or test_script.get_pending_count() > 0):\n\t\t\tlgr.log(\"\\n\" + test_script.get_full_name(), lgr.fmts.underline)\n\n\t\tif(test_script.was_skipped):\n\t\t\tlgr.inc_indent()\n\t\t\tvar skip_msg = str('[Risky] Script was skipped:  ', test_script.skip_reason)\n\t\t\tlgr.log(skip_msg, lgr.fmts.yellow)\n\t\t\tlgr.dec_indent()\n\n\t\tvar test_fail_count = 0\n\t\tfor test in test_script.tests:\n\t\t\tif(test.was_run):\n\t\t\t\tif(test.is_passing()):\n\t\t\t\t\tto_return.passing += 1\n\t\t\t\telse:\n\t\t\t\t\tto_return.non_passing += 1\n\t\t\t\t\tlgr.log(str('- ', test.name))\n\t\t\t\t\tlgr.inc_indent()\n\n\t\t\t\t\tfor i in range(test.fail_texts.size()):\n\t\t\t\t\t\tlgr.failed(test.fail_texts[i])\n\t\t\t\t\t\ttest_fail_count += 1\n\t\t\t\t\tfor i in range(test.pending_texts.size()):\n\t\t\t\t\t\tlgr.pending(test.pending_texts[i])\n\t\t\t\t\tif(test.is_risky()):\n\t\t\t\t\t\tlgr.risky('Did not assert')\n\t\t\t\t\tlgr.dec_indent()\n\n\t\tif(test_script.get_fail_count() > test_fail_count):\n\t\t\tlgr.failed(\"before_all/after_all assert failed\")\n\n\treturn to_return\n\n\nfunc log_the_final_line(totals, gut):\n\tvar lgr = gut.get_logger()\n\tvar grand_total_text = \"\"\n\tvar grand_total_fmt = lgr.fmts.none\n\tif(totals.failing_tests > 0):\n\t\tgrand_total_text = str(totals.failing_tests, \" failing tests\")\n\t\tgrand_total_fmt = lgr.fmts.red\n\telif(totals.failing > 0): # no failing tests, but some failing asserts\n\t\tgrand_total_text = str(totals.failing, \" assert(s) in before_all/after_all methods failed\")\n\t\tgrand_total_fmt = lgr.fmts.red\n\telif(totals.risky > 0 or totals.pending > 0):\n\t\tgrand_total_text = str(totals.risky + totals.pending, \" pending/risky tests.\")\n\t\tgrand_total_fmt = lgr.fmts.yellow\n\telse:\n\t\tgrand_total_text = \"All tests passed!\"\n\t\tgrand_total_fmt = lgr.fmts.green\n\n\tlgr.log(str(\"---- \", grand_total_text, \" ----\"), grand_total_fmt)\n\n\nfunc log_totals(gut, totals):\n\tvar lgr = gut.get_logger()\n\tvar orig_indent = lgr.get_indent_level()\n\tlgr.set_indent_level(0)\n\t_log_totals(gut, totals)\n\tlgr.set_indent_level(orig_indent)\n\n\nfunc get_totals(gut=_gut):\n\tvar tc = gut.get_test_collector()\n\tvar lgr = gut.get_logger()\n\n\tvar totals = {\n\t\tfailing = 0,\n\t\tfailing_tests = 0,\n\t\tpassing = 0,\n\t\tpassing_tests = 0,\n\t\tpending = 0,\n\t\trisky = 0,\n\t\tscripts = tc.get_ran_script_count(),\n\t\ttests = 0,\n\n\t\tdeprecated = lgr.get_deprecated().size(),\n\t\terrors = lgr.get_errors().size(),\n\t\twarnings = lgr.get_warnings().size(),\n\t}\n\n\tfor s in tc.scripts:\n\t\t# assert totals\n\t\ttotals.passing += s.get_pass_count()\n\t\ttotals.pending += s.get_pending_count()\n\t\ttotals.failing += s.get_fail_count()\n\n\t\t# test totals\n\t\ttotals.tests += s.get_ran_test_count()\n\t\ttotals.passing_tests += s.get_passing_test_count()\n\t\ttotals.failing_tests += s.get_failing_test_count()\n\t\ttotals.risky += s.get_risky_count()\n\n\ttotals.orphans = gut.get_orphan_counter().orphan_count()\n\n\treturn totals\n\n\nfunc log_end_run(gut=_gut):\n\tvar totals = get_totals(gut)\n\tif(totals.tests == 0):\n\t\t_log_nothing_run(gut)\n\t\treturn\n\n\t_log_end_run_header(gut)\n\tvar lgr = gut.get_logger()\n\n\tlog_all_non_passing_tests(gut)\n\tlog_totals(gut, totals)\n\tlgr.log(\"\\n\")\n\n\t_log_what_was_run(gut)\n\tlog_the_final_line(totals, gut)\n\tlgr.log(\"\")\n"
  },
  {
    "path": "demo/addons/gut/summary.gd.uid",
    "content": "uid://byxw2c883i2kr\n"
  },
  {
    "path": "demo/addons/gut/test.gd",
    "content": "class_name GutTest\nextends Node\n## This is the base class for your GUT test scripts.[br]\n## [br]\n## GUT Wiki:  [url=https://gut.readthedocs.io]https://gut.readthedocs.io[/url]\n## [br]\n## Simple Example\n## [codeblock]\n##    extends GutTest\n##\n##    func before_all():\n##        gut.p(\"before_all called\"\n##\n##    func before_each():\n##        gut.p(\"before_each called\")\n##\n##    func after_each():\n##        gut.p(\"after_each called\")\n##\n##    func after_all():\n##        gut.p(\"after_all called\")\n##\n##    func test_assert_eq_letters():\n##        assert_eq(\"asdf\", \"asdf\", \"Should pass\")\n##\n##    func test_assert_eq_number_not_equal():\n##        assert_eq(1, 2, \"Should fail.  1 != 2\")\n## [/codeblock]\n\n\n# Normalizes p1 and p2 into object/signal_name/signal_ref(sig).  Additional\n# parameters are optional and will be placed into the others array.  This\n# class is used in refactoring signal methods to accept a reference to the\n# signal instead an object and the signal name.\nclass SignalAssertParameters:\n\tvar object = null\n\tvar signal_name = null\n\tvar sig = null\n\tvar others := []\n\n\tfunc _init(p1, p2, p3=null, p4=null, p5=null, p6=null):\n\t\tothers = [p3, p4, p5, p6]\n\t\tif(p1 is Signal):\n\t\t\tobject = p1.get_object()\n\t\t\tsignal_name = p1.get_name()\n\t\t\tothers.push_front(p2)\n\t\t\tsig = p1\n\t\telse:\n\t\t\tobject = p1\n\t\t\tsignal_name = p2\n\t\t\tsig = object.get(signal_name)\n\n\nconst EDITOR_PROPERTY = PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_DEFAULT\nconst VARIABLE_PROPERTY = PROPERTY_USAGE_SCRIPT_VARIABLE\n# Convenience copy of GutUtils.DOUBLE_STRATEGY\nvar DOUBLE_STRATEGY = GutUtils.DOUBLE_STRATEGY\n\n## Reference to [addons/gut/parameter_factory.gd] script.\nvar ParameterFactory = GutUtils.ParameterFactory\n## @ignore\nvar CompareResult = GutUtils.CompareResult\n## Reference to [GutInputFactory] class that was originally used to reference\n## the Input Factory before the class_name was introduced.\nvar InputFactory = GutInputFactory\n## Reference to [GutInputSender].  This was the way you got to the [GutInputSender]\n## before it was given a [code]class_name[/code]\nvar InputSender = GutUtils.InputSender\n\n# Need a reference to the instance that is running the tests.  This\n# is set by the gut class when it runs the test script.\nvar gut: GutMain = null\n# Reference to the collected_script.gd instance that was used to create this.\n# This makes getting to meta data about the test easier.  This is set by\n# collected_script.get_new().\nvar collected_script = null\nvar wait_log_delay = .5 :\n\tset(val):\n\t\tif(_awaiter != null):\n\t\t\t_awaiter.await_logger.wait_log_delay = val\n\t\t\twait_log_delay = val\nvar _compare = GutUtils.Comparator.new()\nvar _disable_strict_datatype_checks = false\n# Holds all the text for a test's fail/pass.  This is used for testing purposes\n# to see the text of a failed sub-test in test_test.gd\nvar _fail_pass_text = []\n# Summary counts for the test.\nvar _summary = {\n\tasserts = 0,\n\tpassed = 0,\n\tfailed = 0,\n\ttests = 0,\n\tpending = 0\n}\n\n# This is used to watch signals so we can make assertions about them.\nvar _signal_watcher = load('res://addons/gut/signal_watcher.gd').new()\nvar _lgr = GutUtils.get_logger()\nvar _strutils = GutUtils.Strutils.new()\nvar _awaiter = null\nvar _was_ready_called = false\n\n\n# I haven't decided if we should be using _ready or not.  Right now gut.gd will\n# call this if _ready was not called (because it was overridden without a super\n# call).  Maybe gut.gd should just call _do_ready_stuff (after we rename it to\n# something better).  I'm leaving all this as it is until it bothers me more.\nfunc _do_ready_stuff():\n\t_awaiter = GutUtils.Awaiter.new()\n\t_awaiter.await_logger.wait_log_delay = wait_log_delay\n\tadd_child(_awaiter)\n\t_was_ready_called = true\n\n\nfunc _ready():\n\t_do_ready_stuff()\n\n\nfunc _notification(what):\n\t# Tests are never expected to re-enter the tree.  Tests are removed from the\n\t# tree after they are run.\n\tif(what == NOTIFICATION_EXIT_TREE):\n\t\t# print(_strutils.type2str(self), ':  exit_tree')\n\t\t_awaiter.queue_free()\n\telif(what == NOTIFICATION_PREDELETE):\n\t\t# print(_strutils.type2str(self), ':  predelete')\n\t\tif(is_instance_valid(_awaiter)):\n\t\t\t_awaiter.queue_free()\n\n\n#region Private\n# ----------------\n\n\nfunc _str(thing):\n\treturn _strutils.type2str(thing)\n\n\nfunc _str_precision(value, precision):\n\tvar to_return = _str(value)\n\tvar format = str('%.', precision, 'f')\n\tif(typeof(value) == TYPE_FLOAT):\n\t\tto_return = format % value\n\telif(typeof(value) == TYPE_VECTOR2):\n\t\tto_return = str('VECTOR2(', format % value.x, ', ', format %value.y, ')')\n\telif(typeof(value) == TYPE_VECTOR3):\n\t\tto_return = str('VECTOR3(', format % value.x, ', ', format %value.y, ', ', format % value.z, ')')\n\n\treturn to_return\n\n\n# Fail an assertion.  Causes test and script to fail as well.\nfunc _fail(text):\n\t_summary.asserts += 1\n\t_summary.failed += 1\n\t_fail_pass_text.append('failed:  ' + text)\n\tif(gut):\n\t\t_lgr.failed(gut.get_call_count_text() + text)\n\t\tgut._fail(text)\n\n\n# Pass an assertion.\nfunc _pass(text):\n\t_summary.asserts += 1\n\t_summary.passed += 1\n\t_fail_pass_text.append('passed:  ' + text)\n\tif(gut):\n\t\t_lgr.passed(text)\n\t\tgut._pass(text)\n\n\n# Checks if the datatypes passed in match.  If they do not then this will cause\n# a fail to occur.  If they match then TRUE is returned, FALSE if not.  This is\n# used in all the assertions that compare values.\nfunc _do_datatypes_match__fail_if_not(got, expected, text):\n\tvar did_pass = true\n\n\tif(!_disable_strict_datatype_checks):\n\t\tvar got_type = typeof(got)\n\t\tvar expect_type = typeof(expected)\n\t\tif(got_type != expect_type and got != null and expected != null):\n\t\t\t# If we have a mismatch between float and int (types 2 and 3) then\n\t\t\t# print out a warning but do not fail.\n\t\t\tif([2, 3].has(got_type) and [2, 3].has(expect_type)):\n\t\t\t\t_lgr.warn(str('Warn:  Float/Int comparison.  Got ', _strutils.types[got_type],\n\t\t\t\t\t' but expected ', _strutils.types[expect_type]))\n\t\t\telif([TYPE_STRING, TYPE_STRING_NAME].has(got_type) and [TYPE_STRING, TYPE_STRING_NAME].has(expect_type)):\n\t\t\t\tpass\n\t\t\telse:\n\t\t\t\t_fail('Cannot compare ' + _strutils.types[got_type] + '[' + _str(got) + '] to ' + \\\n\t\t\t\t\t_strutils.types[expect_type] + '[' + _str(expected) + '].  ' + text)\n\t\t\t\tdid_pass = false\n\n\treturn did_pass\n\n\n# Create a string that lists all the methods that were called on an spied\n# instance.\nfunc _get_desc_of_calls_to_instance(inst):\n\tvar BULLET = '  * '\n\tvar calls = gut.get_spy().get_call_list_as_string(inst)\n\t# indent all the calls\n\tcalls = BULLET + calls.replace(\"\\n\", \"\\n\" + BULLET)\n\t# remove_at trailing newline and bullet\n\tcalls = calls.substr(0, calls.length() - BULLET.length() - 1)\n\treturn \"Calls made on \" + str(inst) + \"\\n\" + calls\n\n\n\n# Signal assertion helper.  Do not call directly, use _can_make_signal_assertions\nfunc _fail_if_does_not_have_signal(object, signal_name):\n\tvar did_fail = false\n\tif(!_signal_watcher.does_object_have_signal(object, signal_name)):\n\t\t_fail(str('Object ', object, ' does not have the signal [', signal_name, ']'))\n\t\tdid_fail = true\n\treturn did_fail\n\n\n# Signal assertion helper.  Do not call directly, use _can_make_signal_assertions\nfunc _fail_if_not_watching(object):\n\tvar did_fail = false\n\tif(!_signal_watcher.is_watching_object(object)):\n\t\t_fail(str('Cannot make signal assertions because the object ', object, \\\n\t\t\t\t' is not being watched.  Call watch_signals(some_object) to be able to make assertions about signals.'))\n\t\tdid_fail = true\n\treturn did_fail\n\n\n# Returns text that contains original text and a list of all the signals that\n# were emitted for the passed in object.\nfunc _get_fail_msg_including_emitted_signals(text, object):\n\treturn str(text,\" (Signals emitted: \", _signal_watcher.get_signals_emitted(object), \")\")\n\n\n# This validates that parameters is an array and generates a specific error\n# and a failure with a specific message\nfunc _fail_if_parameters_not_array(parameters):\n\tvar invalid = parameters != null and typeof(parameters) != TYPE_ARRAY\n\tif(invalid):\n\t\t_lgr.error('The \"parameters\" parameter must be an array of expected parameter values.')\n\t\t_fail('Cannot compare parameter values because an array was not passed.')\n\treturn invalid\n\n\n# A bunch of common checkes used when validating a double/method pair.  If\n# everything is ok then an empty string is returned, otherwise the message\n# is returned.\nfunc _get_bad_method_message(inst, method_name, what_you_cant_do):\n\tvar to_return = ''\n\n\tif(!inst.has_method(method_name)):\n\t\tto_return = str(\"You cannot \", what_you_cant_do, \" [\", method_name, \"] because the method does not exist.  \",\n\t\t\t\"This can happen if the method is virtual and not overloaded (i.e. _ready) \",\n\t\t\t\"or you have mistyped the name of the method.\")\n\telif(!inst.__gutdbl_values.doubled_methods.has(method_name)):\n\t\tto_return = str(\"You cannot \", what_you_cant_do, \" [\", method_name, \"] because \",\n\t\t\t_str(inst), ' does not overload it or it was ignored with ',\n\t\t\t'ignore_method_when_doubling.  See Doubling ',\n\t\t\t'Strategy in the wiki for details on including non-overloaded ',\n\t\t\t'methods in a double.')\n\n\treturn to_return\n\n\nfunc _fail_if_not_double_or_does_not_have_method(inst, method_name):\n\tvar to_return = OK\n\n\tif(!GutUtils.is_double(inst)):\n\t\t_fail(str(\"An instance of a Double was expected, you passed:  \", _str(inst)))\n\t\tto_return = ERR_INVALID_DATA\n\telse:\n\t\tvar msg = _get_bad_method_message(inst, method_name, 'spy on')\n\t\tif(msg != ''):\n\t\t\t_fail(msg)\n\t\t\tto_return = ERR_INVALID_DATA\n\n\treturn to_return\n\n\nfunc _create_obj_from_type(type):\n\tvar obj = null\n\tif type.is_class(\"PackedScene\"):\n\t\tobj = type.instantiate()\n\t\tadd_child(obj)\n\telse:\n\t\tobj = type.new()\n\treturn obj\n\n\n# Converts a Callabe passed through inst or inst/method_name/parameters into a\n# hash so that methods that interact with Spy can accept both more easily.\nfunc _convert_spy_args(inst, method_name, parameters):\n\tvar to_return = {\n\t\t'object':inst,\n\t\t'method_name':method_name,\n\t\t'arguments':parameters,\n\t\t'invalid_message':'ok'\n\t}\n\n\tif(inst is Callable):\n\t\tif(parameters != null):\n\t\t\tto_return.invalid_message =\\\n\t\t\t\t\"3rd parameter to assert_called not supported when using a Callable.\"\n\t\telif(method_name != null):\n\t\t\tto_return.invalid_message =\\\n\t\t\t\t\"2nd parameter to assert_called not supported when using a Callable.\"\n\t\telse:\n\t\t\tif(inst.get_bound_arguments_count() > 0):\n\t\t\t\tto_return.arguments = inst.get_bound_arguments()\n\t\t\tto_return.method_name = inst.get_method()\n\t\t\tto_return.object = inst.get_object()\n\n\treturn to_return\n\n\nfunc _get_typeof_string(the_type):\n\tvar to_return = \"\"\n\tif(_strutils.types.has(the_type)):\n\t\tto_return += str(the_type, '(',  _strutils.types[the_type], ')')\n\telse:\n\t\tto_return += str(the_type)\n\treturn to_return\n\n\n# Validates the singleton_name is a string and exists.  Errors when conditions\n# are not met.  Returns true/false if singleton_name is valid or not.\nfunc _validate_singleton_name(singleton_name):\n\tvar is_valid = true\n\tif(typeof(singleton_name) != TYPE_STRING):\n\t\t_lgr.error(\"double_singleton requires a Godot singleton name, you passed \" + _str(singleton_name))\n\t\tis_valid = false\n\t# Sometimes they have underscores in front of them, sometimes they do not.\n\t# The doubler is smart enought of ind the right thing, so this has to be\n\t# that smart as well.\n\telif(!ClassDB.class_exists(singleton_name) and !ClassDB.class_exists('_' + singleton_name)):\n\t\tvar txt = str(\"The singleton [\", singleton_name, \"] could not be found.  \",\n\t\t\t\t\t\"Check the GlobalScope page for a list of singletons.\")\n\t\t_lgr.error(txt)\n\t\tis_valid = false\n\treturn is_valid\n\n\n# Checks the object for 'get_' and 'set_' methods for the specified property.\n# If found a warning is generated.\nfunc _warn_for_public_accessors(obj, property_name):\n\tvar public_accessors = []\n\tvar accessor_names = [\n\t\tstr('get_', property_name),\n\t\tstr('is_', property_name),\n\t\tstr('set_', property_name)\n\t]\n\n\tfor acc in accessor_names:\n\t\tif(obj.has_method(acc)):\n\t\t\tpublic_accessors.append(acc)\n\n\tif(public_accessors.size() > 0):\n\t\t_lgr.warn (str('Public accessors ', public_accessors, ' found for property ', property_name))\n\n\nfunc _smart_double(thing, double_strat, partial):\n\tvar override_strat = GutUtils.nvl(double_strat, gut.get_doubler().get_strategy())\n\tvar to_return = null\n\n\tif(thing is PackedScene):\n\t\tif(partial):\n\t\t\tto_return =  gut.get_doubler().partial_double_scene(thing, override_strat)\n\t\telse:\n\t\t\tto_return =  gut.get_doubler().double_scene(thing, override_strat)\n\n\telif(GutUtils.is_native_class(thing)):\n\t\tif(partial):\n\t\t\tto_return = gut.get_doubler().partial_double_gdnative(thing)\n\t\telse:\n\t\t\tto_return = gut.get_doubler().double_gdnative(thing)\n\n\telif(thing is GDScript):\n\t\tif(partial):\n\t\t\tto_return = gut.get_doubler().partial_double(thing, override_strat)\n\t\telse:\n\t\t\tto_return = gut.get_doubler().double(thing, override_strat)\n\n\treturn to_return\n\n\n# This is here to aid in the transition to the new doubling sytnax.  Once this\n# has been established it could be removed.  We must keep the is_instance check\n# going forward though.\nfunc _are_double_parameters_valid(thing, p2, p3):\n\tvar bad_msg = \"\"\n\tif(p3 != null or typeof(p2) == TYPE_STRING):\n\t\tbad_msg += \"Doubling using a subpath is not supported.  Call register_inner_class and then pass the Inner Class to double().\\n\"\n\n\tif(typeof(thing) == TYPE_STRING):\n\t\tbad_msg += \"Doubling using the path to a script or scene is no longer supported.  Load the script or scene and pass that to double instead.\\n\"\n\n\tif(GutUtils.is_instance(thing)):\n\t\tbad_msg += \"double requires a script, you passed an instance:  \" + _str(thing)\n\n\tif(bad_msg != \"\"):\n\t\t_lgr.error(bad_msg)\n\n\treturn bad_msg == \"\"\n\n# ----------------\n#endregion\n#region Virtual Methods\n# ----------------\n\n## Virtual Method.  This is run after the script has been prepped for execution, but before `before_all` is executed.  If you implement this method and return `true` or a `String` (the string is displayed in the log) then GUT will stop executing the script and mark it as risky.  You might want to do this because:\n## - You are porting tests from 3.x to 4.x and you don't want to comment everything out.[br]\n## - Skipping tests that should not be run when in `headless` mode such as input testing that does not work in headless.[br]\n## [codeblock]\n##    func should_skip_script():\n##        if DisplayServer.get_name() == \"headless\":\n##            return \"Skip Input tests when running headless\"\n## [/codeblock]\n## - If you have tests that would normally cause the debugger to break on an error, you can skip the script if the debugger is enabled so that the run is not interrupted.[br]\n## [codeblock]\n##    func should_skip_script():\n##        return EngineDebugger.is_active()\n## [/codeblock]\nfunc should_skip_script():\n\treturn false\n\n\n## Virtual method.  Run once before anything else in the test script is run.\nfunc before_all():\n\tpass\n\n\n## Virtual method.  Run before each test is executed\nfunc before_each():\n\tpass\n\n## Virtual method.  Run after each test is executed.\nfunc after_each():\n\tpass\n\n\n## Virtual method.  Run after all tests have been run.\nfunc after_all():\n\tpass\n\n# ----------------\n#endregion\n#region Misc Public\n# ----------------\n## Mark the current test as pending.\nfunc pending(text=\"\"):\n\t_summary.pending += 1\n\tif(gut):\n\t\t_lgr.pending(text)\n\t\tgut._pending(text)\n\n\n## Returns true if the test is passing as of the time of this call.  False if not.\nfunc is_passing():\n\tif(gut.get_current_test_object() != null and\n\t\t!['before_all', 'after_all'].has(gut.get_current_test_object().name)):\n\t\treturn gut.get_current_test_object().is_passing() and \\\n\t\t\tgut.get_current_test_object().assert_count > 0\n\telse:\n\t\t_lgr.error('No current test object found.  is_passing must be called inside a test.')\n\t\treturn null\n\n\n## Returns true if the test is failing as of the time of this call.  False if not.\nfunc is_failing():\n\tif(gut.get_current_test_object() != null and\n\t\t!['before_all', 'after_all'].has(gut.get_current_test_object().name)):\n\n\t\treturn gut.get_current_test_object().is_failing()\n\telse:\n\t\t_lgr.error('No current test object found.  is_failing must be called inside a test.')\n\t\treturn null\n\n\n## Marks the test as passing.  Does not override any failing asserts or calls to\n## fail_test.  Same as a passing assert.\nfunc pass_test(text):\n\t_pass(text)\n\n\n## Marks the test as failing.  Same as a failing assert.\nfunc fail_test(text):\n\t_fail(text)\n\n## @internal\nfunc clear_signal_watcher():\n\t_signal_watcher.clear()\n\n\n## Returns the current double strategy.\nfunc get_double_strategy():\n\treturn gut.get_doubler().get_strategy()\n\n\n## Sets the double strategy for all tests in the script.  This should usually\n## be done in [method before_all].  The double strtegy can be set per\n## run/script/double.  See [wiki]Double-Strategy[/wiki]\nfunc set_double_strategy(double_strategy):\n\tgut.get_doubler().set_strategy(double_strategy)\n\n\n## This method will cause Gut to pause before it moves on to the next test.\n## This is useful for debugging, for instance if you want to investigate the\n## screen or anything else after a test has finished executing.\n## [br]\n## Sometimes you get lazy, and you don't remove calls to\n## [code skip-lint]pause_before_teardown[/code] after you are done with them.  You can\n## tell GUT to ignore calls to this method through the panel or\n## the command line.  Setting this in your `.gutconfig.json` file is recommended\n## for CI/CD Pipelines.\nfunc pause_before_teardown():\n\tgut.pause_before_teardown()\n\n\n## @internal\nfunc get_logger():\n\treturn _lgr\n\n## @internal\nfunc set_logger(logger):\n\t_lgr = logger\n\n\n## This must be called in order to make assertions based on signals being\n## emitted.  __Right now, this only supports signals that are emitted with 9 or\n## less parameters.__  This can be extended but nine seemed like enough for now.\n## The Godot documentation suggests that the limit is four but in my testing\n## I found you can pass more.\n## [br]\n## This must be called in each test in which you want to make signal based\n## assertions in.  You can call it multiple times with different objects.\n## You should not call it multiple times with the same object in the same test.\n## The objects that are watched are cleared after each test (specifically right\n## before `teardown` is called).  Under the covers, Gut will connect to all the\n## signals an object has and it will track each time they fire.  You can then\n## use the following asserts and methods to verify things are acting correct.\nfunc watch_signals(object):\n\t_signal_watcher.watch_signals(object)\n\n\n## This will return the number of times a signal was fired.  This gives you\n## the freedom to make more complicated assertions if the spirit moves you.\n## This will return -1 if the signal was not fired or the object was not being\n## watched, or if the object does not have the signal.\n## [br][br]\n## Accepts either the object and the signal name or the signal.\nfunc get_signal_emit_count(p1, p2=null):\n\tvar sp = SignalAssertParameters.new(p1, p2)\n\treturn _signal_watcher.get_emit_count(sp.object, sp.signal_name)\n\n\n## If you need to inspect the parameters in order to make more complicate assertions, then this will give you access to\n## the parameters of any watched signal.  This works the same way that\n## [code skip-lint]assert_signal_emitted_with_parameters[/code] does.  It takes an object, signal name, and an optional\n## index.  If the index is not specified then the parameters from the most recent emission will be returned.  If the\n## object is not being watched, the signal was not fired, or the object does not have the signal then `null` will be\n## returned.\n##\n## [br][br]\n## [b]Signatures:[/b][br]\n## - get_signal_parameters([param p1]:Signal, [param p2]:parameter-index (optional))[br]\n## - get_signal_parameters([param p1]:object, [param p2]:signal name, [param p3]:parameter-index (optional)) [br]\n## [br]\n## [b]Examples:[/b]\n## [codeblock]\n## class SignalObject:\n##     signal some_signal\n##     signal other_signal\n##\n##\n## func test_get_signal_parameters():\n##     var obj = SignalObject.new()\n##     watch_signals(obj)\n##     obj.some_signal.emit(1, 2, 3)\n##     obj.some_signal.emit('a', 'b', 'c')\n##\n##     # -- Passing --\n##     # passes because get_signal_parameters returns the most recent emission\n##     # by default\n##     assert_eq(get_signal_parameters(obj, 'some_signal'), ['a', 'b', 'c'])\n##     assert_eq(get_signal_parameters(obj.some_signal), ['a', 'b', 'c'])\n##\n##     assert_eq(get_signal_parameters(obj, 'some_signal', 0), [1, 2, 3])\n##     assert_eq(get_signal_parameters(obj.some_signal, 0), [1, 2, 3])\n##\n##     # if the signal was not fired null is returned\n##     assert_null(get_signal_parameters(obj, 'other_signal'))\n##     # if the signal does not exist or isn't being watched null is returned\n##     assert_null(get_signal_parameters(obj, 'signal_dne'))\n##\n##     # -- Failing --\n##     assert_eq(get_signal_parameters(obj, 'some_signal'), [1, 2, 3])\n##     assert_eq(get_signal_parameters(obj.some_signal, 0), ['a', 'b', 'c'])\n## [/codeblock]\nfunc get_signal_parameters(p1, p2=null, p3=-1):\n\tvar sp := SignalAssertParameters.new(p1, GutUtils.nvl(p2, -1), p3)\n\treturn _signal_watcher.get_signal_parameters(sp.object, sp.signal_name, sp.others[0])\n\n\n## Get the parameters for a method call to a doubled object.  By default it will\n## return the most recent call.  You can optionally specify an index for which\n## call you want to get the parameters for.\n##\n## Can be called using a Callable for the first parameter instead of specifying\n## an object and method name.  When you do this, the seoncd parameter is used\n## as the index.\n##\n## Returns:\n## * an array of parameter values if a call the method was found\n## * null when a call to the method was not found or the index specified was\n##   invalid.\nfunc get_call_parameters(object, method_name_or_index = -1, idx=-1):\n\tvar to_return = null\n\tvar index = idx\n\tif(object is Callable):\n\t\tindex = method_name_or_index\n\t\tmethod_name_or_index = null\n\tvar converted = _convert_spy_args(object, method_name_or_index, null)\n\n\tif(GutUtils.is_double(converted.object)):\n\t\tto_return = gut.get_spy().get_call_parameters(\n\t\t\tconverted.object, converted.method_name, index)\n\telse:\n\t\t_lgr.error('You must pass a doulbed object to get_call_parameters.')\n\n\treturn to_return\n\n\n## Returns the call count for a method with optional paramter matching.\n##\n## Can be called with a Callable instead of an object, method_name, and\n## parameters.  Bound arguments will be used to match call arguments.\nfunc get_call_count(object, method_name=null, parameters=null):\n\tvar converted = _convert_spy_args(object, method_name, parameters)\n\treturn gut.get_spy().call_count(converted.object, converted.method_name, converted.arguments)\n\n\n## Simulate a number of frames by calling '_process' and '_physics_process' (if\n## the methods exist) on an object and all of its descendents. The specified frame\n## time, 'delta', will be passed to each simulated call.\n##\n## NOTE: Objects can disable their processing methods using 'set_process(false)' and\n## 'set_physics_process(false)'. This is reflected in the 'Object' methods\n## 'is_processing()' and 'is_physics_processing()', respectively. To make 'simulate'\n## respect this status, for example if you are testing an object which toggles\n## processing, pass 'check_is_processing' as 'true'.\nfunc simulate(obj, times, delta, check_is_processing: bool = false):\n\tgut.simulate(obj, times, delta, check_is_processing)\n\n\n# ------------------------------------------------------------------------------\n## Replace the node at base_node.get_node(path) with with_this.  All references\n## to the node via $ and get_node(...) will now return with_this.  with_this will\n## get all the groups that the node that was replaced had.\n## [br]\n## The node that was replaced is queued to be freed.\n## [br]\n## TODO see replace_by method, this could simplify the logic here.\n# ------------------------------------------------------------------------------\nfunc replace_node(base_node, path_or_node, with_this):\n\tvar path = path_or_node\n\n\tif(typeof(path_or_node) != TYPE_STRING):\n\t\t# This will cause an engine error if it fails.  It always returns a\n\t\t# NodePath, even if it fails.  Checking the name count is the only way\n\t\t# I found to check if it found something or not (after it worked I\n\t\t# didn't look any farther).\n\t\tpath = base_node.get_path_to(path_or_node)\n\t\tif(path.get_name_count() == 0):\n\t\t\t_lgr.error('You passed an object that base_node does not have.  Cannot replace node.')\n\t\t\treturn\n\n\tif(!base_node.has_node(path)):\n\t\t_lgr.error(str('Could not find node at path [', path, ']'))\n\t\treturn\n\n\tvar to_replace = base_node.get_node(path)\n\tvar parent = to_replace.get_parent()\n\tvar replace_name = to_replace.get_name()\n\n\tparent.remove_child(to_replace)\n\tparent.add_child(with_this)\n\twith_this.set_name(replace_name)\n\twith_this.set_owner(parent)\n\n\tvar groups = to_replace.get_groups()\n\tfor i in range(groups.size()):\n\t\twith_this.add_to_group(groups[i])\n\n\tto_replace.queue_free()\n\n\n## Use this as the default value for the first parameter to a test to create\n## a parameterized test.  See also the ParameterFactory and Parameterized Tests.\n## [br][br]\n## [b]Example[/b]\n## [codeblock]\n##    func test_with_parameters(p = use_parameters([1, 2, 3])):\n## [/codeblock]\nfunc use_parameters(params):\n\tvar ph = gut.parameter_handler\n\tif(ph == null):\n\t\tph = GutUtils.ParameterHandler.new(params)\n\t\tgut.parameter_handler = ph\n\n\t# DO NOT use gut.gd's get_call_count_text here since it decrements the\n\t# get_call_count value.  This method increments the call count in its\n\t# return statement.\n\tvar output = str('- params[', ph.get_call_count(), ']','(', ph.get_current_parameters(), ')')\n\tgut.p(output, gut.LOG_LEVEL_TEST_AND_FAILURES)\n\n\treturn ph.next_parameters()\n\n\n## @internal\n## When used as the default for a test method parameter, it will cause the test\n## to be run x times.\n##\n## I Hacked this together to test a method that was occassionally failing due to\n## timing issues.  I don't think it's a great idea, but you be the judge.  If\n## you find a good use for it, let me know and I'll make it a legit member\n## of the api.\nfunc run_x_times(x):\n\tvar ph = gut.parameter_handler\n\tif(ph == null):\n\t\t_lgr.warn(\n\t\t\tstr(\"This test uses run_x_times and you really should not be \",\n\t\t\t\"using it.  I don't think it's a good thing, but I did find it \",\n\t\t\t\"temporarily useful so I left it in here and didn't document it.  \",\n\t\t\t\"Well, you found it, might as well open up an issue and let me \",\n\t\t\t\"know why you're doing this.\"))\n\t\tvar params = []\n\t\tfor i in range(x):\n\t\t\tparams.append(i)\n\n\t\tph = GutUtils.ParameterHandler.new(params)\n\t\tgut.parameter_handler = ph\n\treturn ph.next_parameters()\n\n\n## Checks the passed in version string (x.x.x) against the engine version to see\n## if the engine version is less than the expected version.  If it is then the\n## test is mareked as passed (for a lack of anything better to do).  The result\n## of the check is returned.\n## [br][br]\n## [b]Example[/b]\n## [codeblock]\n##    if(skip_if_godot_version_lt('3.5.0')):\n##        return\n## [/codeblock]\nfunc skip_if_godot_version_lt(expected):\n\tvar should_skip = !GutUtils.is_godot_version_gte(expected)\n\tif(should_skip):\n\t\t_pass(str('Skipping: ', GutUtils.godot_version_string(), ' is less than ', expected))\n\treturn should_skip\n\n\n## Checks if the passed in version matches the engine version.  The passed in\n## version can contain just the major, major.minor or major.minor.path.  If\n## the version is not the same then the test is marked as passed.  The result of\n## the check is returned.\n## [br][br]\n## [b]Example[/b]\n## [codeblock]\n##     if(skip_if_godot_version_ne('3.4')):\n##        return\n## [/codeblock]\nfunc skip_if_godot_version_ne(expected):\n\tvar should_skip = !GutUtils.is_godot_version(expected)\n\tif(should_skip):\n\t\t_pass(str('Skipping: ', GutUtils.godot_version_string(), ' is not ', expected))\n\treturn should_skip\n\n\n## Registers all the inner classes in a script with the doubler.  This is required\n## before you can double any inner class.\nfunc register_inner_classes(base_script):\n\tgut.get_doubler().inner_class_registry.register(base_script)\n\n\n## Peforms a deep compare on both values, a CompareResult instnace is returned.\n## The optional max_differences paramter sets the max_differences to be displayed.\nfunc compare_deep(v1, v2, max_differences=null):\n\tvar result = _compare.deep(v1, v2)\n\tif(max_differences != null):\n\t\tresult.max_differences = max_differences\n\treturn result\n\n\n# ----------------\n#endregion\n#region Asserts\n# ----------------\n\n## Asserts that the expected value equals the value got.\n## assert got == expected and prints optional text.  See [wiki]Comparing-Things[/wiki]\n## for information about comparing dictionaries and arrays.\n## [br]\n## See also: [method assert_ne], [method assert_same], [method assert_not_same]\n## [codeblock]\n##    var one = 1\n##    var node1 = Node.new()\n##    var node2 = node1\n##\n##    # Passing\n##    assert_eq(one, 1, 'one should equal one')\n##    assert_eq('racecar', 'racecar')\n##    assert_eq(node2, node1)\n##    assert_eq([1, 2, 3], [1, 2, 3])\n##    var d1_pass = {'a':1}\n##    var d2_pass = d1_pass\n##    assert_eq(d1_pass, d2_pass)\n##\n##    # Failing\n##    assert_eq(1, 2) # FAIL\n##    assert_eq('hello', 'world')\n##    assert_eq(self, node1)\n##    assert_eq([1, 'two', 3], [1, 2, 3, 4])\n##    assert_eq({'a':1}, {'a':1})\n## [/codeblock]\nfunc assert_eq(got, expected, text=\"\"):\n\n\tif(_do_datatypes_match__fail_if_not(got, expected, text)):\n\t\tvar disp = \"[\" + _str(got) + \"] expected to equal [\" + _str(expected) + \"]:  \" + text\n\t\tvar result = null\n\n\t\tresult = _compare.simple(got, expected)\n\n\t\tif(typeof(got) in [TYPE_ARRAY, TYPE_DICTIONARY]):\n\t\t\tdisp = str(result.summary, '  ', text)\n\t\t\t_lgr.info('Array/Dictionary compared by value.  Use assert_same to compare references.  Use assert_eq_deep to see diff when failing.')\n\n\t\tif(result.are_equal):\n\t\t\t_pass(disp)\n\t\telse:\n\t\t\t_fail(disp)\n\n\n## asserts got != expected and prints optional text.  See\n## [wiki]Comparing-Things[/wiki] for information about comparing dictionaries\n## and arrays.\n##[br]\n## See also: [method assert_eq], [method assert_same], [method assert_not_same]\n## [codeblock]\n##    var two = 2\n##    var node1 = Node.new()\n##\n##    # Passing\n##    assert_ne(two, 1, 'Two should not equal one.')\n##    assert_ne('hello', 'world')\n##    assert_ne(self, node1)\n##\n##    # Failing\n##    assert_ne(two, 2)\n##    assert_ne('one', 'one')\n##    assert_ne('2', 2)\n## [/codeblock]\nfunc assert_ne(got, not_expected, text=\"\"):\n\tif(_do_datatypes_match__fail_if_not(got, not_expected, text)):\n\t\tvar disp = \"[\" + _str(got) + \"] expected to not equal [\" + _str(not_expected) + \"]:  \" + text\n\t\tvar result = null\n\n\t\tresult = _compare.simple(got, not_expected)\n\n\t\tif(typeof(got) in [TYPE_ARRAY, TYPE_DICTIONARY]):\n\t\t\tdisp = str(result.summary, '  ', text)\n\t\t\t_lgr.info('Array/Dictionary compared by value.  Use assert_not_same to compare references.  Use assert_ne_deep to see diff.')\n\n\t\tif(result.are_equal):\n\t\t\t_fail(disp)\n\t\telse:\n\t\t\t_pass(disp)\n\n\n## Asserts that [param got] is within the range of [param expected] +/- [param error_interval].\n## The upper and lower bounds are included in the check.  Verified to work with\n## integers, floats, and Vector2.  Should work with anything that can be\n## added/subtracted.\n##\n## [codeblock]\n##    # Passing\n##    assert_almost_eq(0, 1, 1, '0 within range of 1 +/- 1')\n##    assert_almost_eq(2, 1, 1, '2 within range of 1 +/- 1')\n##    assert_almost_eq(1.2, 1.0, .5, '1.2 within range of 1 +/- .5')\n##    assert_almost_eq(.5, 1.0, .5, '.5 within range of 1 +/- .5')\n##    assert_almost_eq(Vector2(.5, 1.5), Vector2(1.0, 1.0), Vector2(.5, .5))\n##    assert_almost_eq(Vector2(.5, 1.5), Vector2(1.0, 1.0), Vector2(.25, .25))\n##\n##    # Failing\n##    assert_almost_eq(1, 3, 1, '1 outside range of 3 +/- 1')\n##    assert_almost_eq(2.6, 3.0, .2, '2.6 outside range of 3 +/- .2')\n## [/codeblock]\nfunc assert_almost_eq(got, expected, error_interval, text=''):\n\tvar disp = \"[\" + _str_precision(got, 20) + \"] expected to equal [\" + _str(expected) + \"] +/- [\" + str(error_interval) + \"]:  \" + text\n\tif(_do_datatypes_match__fail_if_not(got, expected, text) and _do_datatypes_match__fail_if_not(got, error_interval, text)):\n\t\tif not _is_almost_eq(got, expected, error_interval):\n\t\t\t_fail(disp)\n\t\telse:\n\t\t\t_pass(disp)\n\n\n## This is the inverse of [method assert_almost_eq].  This will pass if [param got] is\n## outside the range of [param not_expected] +/- [param error_interval].\nfunc assert_almost_ne(got, not_expected, error_interval, text=''):\n\tvar disp = \"[\" + _str_precision(got, 20) + \"] expected to not equal [\" + _str(not_expected) + \"] +/- [\" + str(error_interval) + \"]:  \" + text\n\tif(_do_datatypes_match__fail_if_not(got, not_expected, text) and _do_datatypes_match__fail_if_not(got, error_interval, text)):\n\t\tif _is_almost_eq(got, not_expected, error_interval):\n\t\t\t_fail(disp)\n\t\telse:\n\t\t\t_pass(disp)\n\n# ------------------------------------------------------------------------------\n# Helper function compares a value against a expected and a +/- range.  Compares\n# all components of Vector2, Vector3, and Vector4 as well.\n# ------------------------------------------------------------------------------\nfunc _is_almost_eq(got, expected, error_interval) -> bool:\n\tvar result = false\n\tvar upper = expected + error_interval\n\tvar lower = expected - error_interval\n\n\tif typeof(got) in [TYPE_VECTOR2, TYPE_VECTOR3, TYPE_VECTOR4]:\n\t\tresult = got.clamp(lower, upper) == got\n\telse:\n\t\tresult = got >= (lower) and got <= (upper)\n\n\treturn(result)\n\n## assserts got > expected\n## [codeblock]\n##    var bigger = 5\n##    var smaller = 0\n##\n##    # Passing\n##    assert_gt(bigger, smaller, 'Bigger should be greater than smaller')\n##    assert_gt('b', 'a')\n##    assert_gt('a', 'A')\n##    assert_gt(1.1, 1)\n##\n##    # Failing\n##    assert_gt('a', 'a')\n##    assert_gt(1.0, 1)\n##    assert_gt(smaller, bigger)\n## [/codeblock]\nfunc assert_gt(got, expected, text=\"\"):\n\tvar disp = \"[\" + _str(got) + \"] expected to be > than [\" + _str(expected) + \"]:  \" + text\n\tif(_do_datatypes_match__fail_if_not(got, expected, text)):\n\t\tif(got > expected):\n\t\t\t_pass(disp)\n\t\telse:\n\t\t\t_fail(disp)\n\n\n## Asserts got is greater than or equal to expected.\n## [codeblock]\n##    var bigger = 5\n##    var smaller = 0\n##\n##    # Passing\n##    assert_gte(bigger, smaller, 'Bigger should be greater than or equal to smaller')\n##    assert_gte('b', 'a')\n##    assert_gte('a', 'A')\n##    assert_gte(1.1, 1)\n##    assert_gte('a', 'a')\n##\n##    # Failing\n##    assert_gte(0.9, 1.0)\n##    assert_gte(smaller, bigger)\n## [/codeblock]\nfunc assert_gte(got, expected, text=\"\"):\n\tvar disp = \"[\" + _str(got) + \"] expected to be >= than [\" + _str(expected) + \"]:  \" + text\n\tif(_do_datatypes_match__fail_if_not(got, expected, text)):\n\t\tif(got >= expected):\n\t\t\t_pass(disp)\n\t\telse:\n\t\t\t_fail(disp)\n\n## Asserts [param got] is less than [param expected]\n## [codeblock]\n##    var bigger = 5\n##    var smaller = 0\n##\n##    # Passing\n##    assert_lt(smaller, bigger, 'Smaller should be less than bigger')\n##    assert_lt('a', 'b')\n##    assert_lt(99, 100)\n##\n##    # Failing\n##    assert_lt('z', 'x')\n##    assert_lt(-5, -5)\n## [/codeblock]\nfunc assert_lt(got, expected, text=\"\"):\n\tvar disp = \"[\" + _str(got) + \"] expected to be < than [\" + _str(expected) + \"]:  \" + text\n\tif(_do_datatypes_match__fail_if_not(got, expected, text)):\n\t\tif(got < expected):\n\t\t\t_pass(disp)\n\t\telse:\n\t\t\t_fail(disp)\n\n\n## Asserts got is less than or equal to expected\nfunc assert_lte(got, expected, text=\"\"):\n\tvar disp = \"[\" + _str(got) + \"] expected to be <= than [\" + _str(expected) + \"]:  \" + text\n\tif(_do_datatypes_match__fail_if_not(got, expected, text)):\n\t\tif(got <= expected):\n\t\t\t_pass(disp)\n\t\telse:\n\t\t\t_fail(disp)\n\n\n## asserts that got is true.  Does not assert truthiness, only boolean values\n## will pass.\nfunc assert_true(got, text=\"\"):\n\tif(typeof(got) == TYPE_BOOL):\n\t\tif(got):\n\t\t\t_pass(text)\n\t\telse:\n\t\t\t_fail(text)\n\telse:\n\t\tvar msg = str(\"Cannot convert \", _strutils.type2str(got), \" to boolean\")\n\t\t_fail(msg)\n\n\n## Asserts that got is false.  Does not assert truthiness, only boolean values\n## will pass.\nfunc assert_false(got, text=\"\"):\n\tif(typeof(got) == TYPE_BOOL):\n\t\tif(got):\n\t\t\t_fail(text)\n\t\telse:\n\t\t\t_pass(text)\n\telse:\n\t\tvar msg = str(\"Cannot convert \", _strutils.type2str(got), \" to boolean\")\n\t\t_fail(msg)\n\n\n## Asserts value is between (inclusive) the two expected values.[br]\n## got >= expect_low and <= expect_high\n## [codeblock]\n##    # Passing\n##    assert_between(5, 0, 10, 'Five should be between 0 and 10')\n##    assert_between(10, 0, 10)\n##    assert_between(0, 0, 10)\n##    assert_between(2.25, 2, 4.0)\n##\n##    # Failing\n##    assert_between('a', 'b', 'c')\n##    assert_between(1, 5, 10)\n## [/codeblock]\nfunc assert_between(got, expect_low, expect_high, text=\"\"):\n\tvar disp = \"[\" + _str_precision(got, 20) + \"] expected to be between [\" + _str(expect_low) + \"] and [\" + str(expect_high) + \"]:  \" + text\n\n\tif(_do_datatypes_match__fail_if_not(got, expect_low, text) and _do_datatypes_match__fail_if_not(got, expect_high, text)):\n\t\tif(expect_low > expect_high):\n\t\t\tdisp = \"INVALID range.  [\" + str(expect_low) + \"] is not less than [\" + str(expect_high) + \"]\"\n\t\t\t_fail(disp)\n\t\telse:\n\t\t\tif(got < expect_low or got > expect_high):\n\t\t\t\t_fail(disp)\n\t\t\telse:\n\t\t\t\t_pass(disp)\n\n\n## Asserts value is not between (exclusive) the two expected values.[br]\n## asserts that got <= expect_low or got >=  expect_high.\n## [codeblock]\n##    # Passing\n##    assert_not_between(1, 5, 10)\n##    assert_not_between('a', 'b', 'd')\n##    assert_not_between('d', 'b', 'd')\n##    assert_not_between(10, 0, 10)\n##    assert_not_between(-2, -2, 10)\n##\n##    # Failing\n##    assert_not_between(5, 0, 10, 'Five shouldnt be between 0 and 10')\n##    assert_not_between(0.25, -2.0, 4.0)\n## [/codeblock]\nfunc assert_not_between(got, expect_low, expect_high, text=\"\"):\n\tvar disp = \"[\" + _str_precision(got, 20) + \"] expected not to be between [\" + _str(expect_low) + \"] and [\" + str(expect_high) + \"]:  \" + text\n\n\tif(_do_datatypes_match__fail_if_not(got, expect_low, text) and _do_datatypes_match__fail_if_not(got, expect_high, text)):\n\t\tif(expect_low > expect_high):\n\t\t\tdisp = \"INVALID range.  [\" + str(expect_low) + \"] is not less than [\" + str(expect_high) + \"]\"\n\t\t\t_fail(disp)\n\t\telse:\n\t\t\tif(got > expect_low and got < expect_high):\n\t\t\t\t_fail(disp)\n\t\t\telse:\n\t\t\t\t_pass(disp)\n\n\n## Uses the 'has' method of the object passed in to determine if it contains\n## the passed in element.\n## [codeblock]\n##    var an_array = [1, 2, 3, 'four', 'five']\n##    var a_hash = { 'one':1, 'two':2, '3':'three'}\n##\n##    # Passing\n##    assert_has(an_array, 'four') # PASS\n##    assert_has(an_array, 2) # PASS\n##    # the hash's has method checks indexes not values\n##    assert_has(a_hash, 'one') # PASS\n##    assert_has(a_hash, '3') # PASS\n##\n##    # Failing\n##    assert_has(an_array, 5) # FAIL\n##    assert_has(an_array, self) # FAIL\n##    assert_has(a_hash, 3) # FAIL\n##    assert_has(a_hash, 'three') # FAIL\n## [/codeblock]\nfunc assert_has(obj, element, text=\"\"):\n\tvar disp = str('Expected [', _str(obj), '] to contain value:  [', _str(element), ']:  ', text)\n\tif(obj.has(element)):\n\t\t_pass(disp)\n\telse:\n\t\t_fail(disp)\n\n\n## The inverse of assert_has.\nfunc assert_does_not_have(obj, element, text=\"\"):\n\tvar disp = str('Expected [', _str(obj), '] to NOT contain value:  [', _str(element), ']:  ', text)\n\tif(obj.has(element)):\n\t\t_fail(disp)\n\telse:\n\t\t_pass(disp)\n\n\n## asserts a file exists at the specified path\n## [codeblock]\n##    func before_each():\n##        gut.file_touch('user://some_test_file')\n##\n##    func after_each():\n##        gut.file_delete('user://some_test_file')\n##\n##    func test_assert_file_exists():\n##        # Passing\n##        assert_file_exists('res://addons/gut/gut.gd')\n##        assert_file_exists('user://some_test_file')\n##\n##        # Failing\n##        assert_file_exists('user://file_does_not.exist')\n##        assert_file_exists('res://some_dir/another_dir/file_does_not.exist')\n## [/codeblock]\nfunc assert_file_exists(file_path):\n\tvar disp = 'expected [' + file_path + '] to exist.'\n\tif(FileAccess.file_exists(file_path)):\n\t\t_pass(disp)\n\telse:\n\t\t_fail(disp)\n\n\n## asserts a file does not exist at the specified path\n## [codeblock]\n##    func before_each():\n##        gut.file_touch('user://some_test_file')\n##\n##    func after_each():\n##        gut.file_delete('user://some_test_file')\n##\n##    func test_assert_file_does_not_exist():\n##        # Passing\n##        assert_file_does_not_exist('user://file_does_not.exist')\n##        assert_file_does_not_exist('res://some_dir/another_dir/file_does_not.exist')\n##\n##        # Failing\n##        assert_file_does_not_exist('res://addons/gut/gut.gd')\n## [/codeblock]\nfunc assert_file_does_not_exist(file_path):\n\tvar disp = 'expected [' + file_path + '] to NOT exist'\n\tif(!FileAccess.file_exists(file_path)):\n\t\t_pass(disp)\n\telse:\n\t\t_fail(disp)\n\n\n## asserts the specified file is empty\n## [codeblock]\n##    func before_each():\n##        gut.file_touch('user://some_test_file')\n##\n##    func after_each():\n##        gut.file_delete('user://some_test_file')\n##\n##    func test_assert_file_empty():\n##        # Passing\n##        assert_file_empty('user://some_test_file')\n##\n##        # Failing\n##        assert_file_empty('res://addons/gut/gut.gd')\n## [/codeblock]\nfunc assert_file_empty(file_path):\n\tvar disp = 'expected [' + file_path + '] to be empty'\n\tif(FileAccess.file_exists(file_path) and gut.is_file_empty(file_path)):\n\t\t_pass(disp)\n\telse:\n\t\t_fail(disp)\n\n\n## Asserts the specified file is not empty\n## [codeblock]\n##    func before_each():\n##        gut.file_touch('user://some_test_file')\n##\n##    func after_each():\n##        gut.file_delete('user://some_test_file')\n##\n##    func test_assert_file_not_empty():\n##        # Passing\n##        assert_file_not_empty('res://addons/gut/gut.gd') # PASS\n##\n##        # Failing\n##        assert_file_not_empty('user://some_test_file') # FAIL\n## [/codeblock]\nfunc assert_file_not_empty(file_path):\n\tvar disp = 'expected [' + file_path + '] to contain data'\n\tif(!gut.is_file_empty(file_path)):\n\t\t_pass(disp)\n\telse:\n\t\t_fail(disp)\n\n\n## Asserts that the passed in object has a method named [param method].\nfunc assert_has_method(obj, method, text=''):\n\tvar disp = _str(obj) + ' should have method: ' + method\n\tif(text != ''):\n\t\tdisp = _str(obj) + ' ' + text\n\tassert_true(obj.has_method(method), disp)\n\n\n## This is meant to make testing public get/set methods for a member variable.  This was originally created for early Godot 3.x setter and getter methods.  See [method assert_property] for verifying Godot 4.x accessors.  This makes multiple assertions to verify:\n## [br]\n## [li]The object has a method called [code]get_<PROPERTY_NAME>[/code][/li]\n## [li]The object has a method called [code]set_<PROPERTY_NAME>[/code][/li]\n## [li]The method [code]get_<PROPERTY_NAME>[/code] returns the expected default value when first called.[/li]\n## [li]Once you set the property, the [code]get_<PROPERTY_NAME>[/code] returns the new value.[/li]\n## [br]\nfunc assert_accessors(obj, property, default, set_to):\n\tvar fail_count = _summary.failed\n\tvar get_func = 'get_' + property\n\tvar set_func = 'set_' + property\n\n\tif(obj.has_method('is_' + property)):\n\t\tget_func = 'is_' + property\n\n\tassert_has_method(obj, get_func, 'should have getter starting with get_ or is_')\n\tassert_has_method(obj, set_func)\n\t# SHORT CIRCUIT\n\tif(_summary.failed > fail_count):\n\t\treturn\n\tassert_eq(obj.call(get_func), default, 'It should have the expected default value.')\n\tobj.call(set_func, set_to)\n\tassert_eq(obj.call(get_func), set_to, 'The set value should have been returned.')\n\n\n# Property search helper.  Used to retrieve Dictionary of specified property\n# from passed object. Returns null if not found.\n# If provided, property_usage constrains the type of property returned by\n# passing either:\n# EDITOR_PROPERTY for properties defined as: export var some_value: int\n# VARIABLE_PROPERTY for properties defined as: var another_value\nfunc _find_object_property(obj, property_name, property_usage=null):\n\tvar result = null\n\tvar found = false\n\tvar properties = obj.get_property_list()\n\n\twhile !found and !properties.is_empty():\n\t\tvar property = properties.pop_back()\n\t\tif property['name'] == property_name:\n\t\t\tif property_usage == null or property['usage'] == property_usage:\n\t\t\t\tresult = property\n\t\t\t\tfound = true\n\treturn result\n\n\n## Asserts that [param obj] exports a property with the name\n## [param property_name] and a type of [param type].  The [param type] must be\n## one of the various Godot built-in [code]TYPE_[/code] constants.\n## [codeblock]\n##    class ExportClass:\n##        export var some_number = 5\n##        export(PackedScene) var some_scene\n##        var some_variable = 1\n##\n##    func test_assert_exports():\n##        var obj = ExportClass.new()\n##\n##        # Passing\n##        assert_exports(obj, \"some_number\", TYPE_INT)\n##        assert_exports(obj, \"some_scene\", TYPE_OBJECT)\n##\n##        # Failing\n##        assert_exports(obj, 'some_number', TYPE_VECTOR2)\n##        assert_exports(obj, 'some_scene', TYPE_AABB)\n##        assert_exports(obj, 'some_variable', TYPE_INT)\n## [/codeblock]\nfunc assert_exports(obj, property_name, type):\n\tvar disp = 'expected %s to have editor property [%s]' % [_str(obj), property_name]\n\tvar property = _find_object_property(obj, property_name, EDITOR_PROPERTY)\n\tif property != null:\n\t\tdisp += ' of type [%s]. Got type [%s].' % [_strutils.types[type], _strutils.types[property['type']]]\n\t\tif property['type'] == type:\n\t\t\t_pass(disp)\n\t\telse:\n\t\t\t_fail(disp)\n\telse:\n\t\t_fail(disp)\n\n\n# Signal assertion helper.\n#\n# Verifies that the object and signal are valid for making signal assertions.\n# This will fail with specific messages that indicate why they are not valid.\n# This returns true/false to indicate if the object and signal are valid.\nfunc _can_make_signal_assertions(object, signal_name):\n\treturn !(_fail_if_not_watching(object) or _fail_if_does_not_have_signal(object, signal_name))\n\n\n# Check if an object is connected to a signal on another object. Returns True\n# if it is and false otherwise\nfunc _is_connected(signaler_obj, connect_to_obj, signal_name, method_name=\"\"):\n\tif(method_name != \"\"):\n\t\treturn signaler_obj.is_connected(signal_name,Callable(connect_to_obj,method_name))\n\telse:\n\t\tvar connections = signaler_obj.get_signal_connection_list(signal_name)\n\t\tfor conn in connections:\n\t\t\tif(conn['signal'].get_name() == signal_name and conn['callable'].get_object() == connect_to_obj):\n\t\t\t\treturn true\n\t\treturn false\n\n\n## Asserts that `signaler_obj` is connected to `connect_to_obj` on signal `signal_name`.  The method that is connected is optional.  If `method_name` is supplied then this will pass only if the signal is connected to the  method.  If it is not provided then any connection to the signal will cause a pass.\n## [br][br]\n## [b]Signatures:[/b][br]\n## - assert_connected([param p1]:Signal, [param p2]:connected-object)[br]\n## - assert_connected([param p1]:Signal, [param p2]:connected-method)[br]\n## - assert_connected([param p1]:object, [param p2]:connected-object, [param p3]:signal-name, [param p4]: connected-method-name <optional>)\n## [br][br]\n## [b]Examples:[/b]\n## [codeblock]\n## class Signaler:\n##     signal the_signal\n##\n## class Connector:\n##     func connect_this():\n##         pass\n##     func  other_method():\n##         pass\n##\n## func test_assert_connected():\n##     var signaler = Signaler.new()\n##     var connector  = Connector.new()\n##     signaler.the_signal.connect(connector.connect_this)\n##\n##     # Passing\n##     assert_connected(signaler.the_signal, connector.connect_this)\n##     assert_connected(signaler.the_signal, connector)\n##     assert_connected(signaler, connector, 'the_signal')\n##     assert_connected(signaler, connector, 'the_signal', 'connect_this')\n##\n##     # Failing\n##     assert_connected(signaler.the_signal, connector.other_method)\n##\n##     var foo = Connector.new()\n##     assert_connected(signaler,  connector, 'the_signal', 'other_method')\n##     assert_connected(signaler, connector, 'other_signal')\n##     assert_connected(signaler, foo, 'the_signal')\n## [/codeblock]\nfunc assert_connected(p1, p2, p3=null, p4=\"\"):\n\tvar sp := SignalAssertParameters.new(p1, p3)\n\tvar connect_to_obj = p2\n\tvar method_name = p4\n\n\tif(connect_to_obj is  Callable):\n\t\tmethod_name = connect_to_obj.get_method()\n\t\tconnect_to_obj = connect_to_obj.get_object()\n\n\tvar method_disp = ''\n\tif (method_name != \"\"):\n\t\tmethod_disp = str(' using method: [', method_name, '] ')\n\tvar disp = str('Expected object ', _str(sp.object),\\\n\t\t' to be connected to signal: [', sp.signal_name, '] on ',\\\n\t\t_str(connect_to_obj), method_disp)\n\tif(_is_connected(sp.object, connect_to_obj, sp.signal_name, method_name)):\n\t\t_pass(disp)\n\telse:\n\t\t_fail(disp)\n\n\n## The inverse of [method assert_connected].  See [method assert_connected] for parameter syntax.\n## [br]\n## This will fail with specific messages if the target object is connected to the specified signal on the source object.\nfunc assert_not_connected(p1, p2, p3=null, p4=\"\"):\n\tvar sp := SignalAssertParameters.new(p1, p3)\n\tvar connect_to_obj = p2\n\tvar method_name = p4\n\n\tif(connect_to_obj is  Callable):\n\t\tmethod_name = connect_to_obj.get_method()\n\t\tconnect_to_obj = connect_to_obj.get_object()\n\n\tvar method_disp = ''\n\tif (method_name != \"\"):\n\t\tmethod_disp = str(' using method: [', method_name, '] ')\n\tvar disp = str('Expected object ', _str(sp.object),\\\n\t\t' to not be connected to signal: [', sp.signal_name, '] on ',\\\n\t\t_str(sp.object), method_disp)\n\tif(_is_connected(sp.object, connect_to_obj, sp.signal_name, method_name)):\n\t\t_fail(disp)\n\telse:\n\t\t_pass(disp)\n\n\n## Assert that the specified object emitted the named signal.  You must call\n## [method watch_signals] and pass it the object that you are making assertions about.\n## This will fail if the object is not being watched or if the object does not\n## have the specified signal.  Since this will fail if the signal does not\n## exist, you can often skip using [method assert_has_signal].\n## [br][br]\n## [b]Signatures:[/b][br]\n## - assert_signal_emitted([param p1]:Signal, [param p2]: text <optional>)[br]\n## - assert_signal_emitted([param p1]:object, [param p2]:signal-name, [param p3]: text <optional>)\n## [br][br]\n## [b]Examples:[/b]\n## [codeblock]\n## class SignalObject:\n##     signal some_signal\n##     signal other_signal\n##\n##\n## func test_assert_signal_emitted():\n##     var obj = SignalObject.new()\n##\n##     watch_signals(obj)\n##     obj.emit_signal('some_signal')\n##\n##     ## Passing\n##     assert_signal_emitted(obj, 'some_signal')\n##     assert_signal_emitted(obj.some_signal)\n##\n##     ## Failing\n##     # Fails with specific message that the object does not have the signal\n##     assert_signal_emitted(obj, 'signal_does_not_exist')\n##     # Fails because the object passed is not being watched\n##     assert_signal_emitted(SignalObject.new(), 'some_signal')\n##     # Fails because the signal was not emitted\n##     assert_signal_emitted(obj, 'other_signal')\n##     assert_signal_emitted(obj.other_signal)\n## [/codeblock]\nfunc assert_signal_emitted(p1, p2='', p3=\"\"):\n\tvar sp := SignalAssertParameters.new(p1, p2, p3)\n\tvar disp = str('Expected object ', _str(sp.object), ' to have emitted signal [', sp.signal_name, ']:  ', sp.others[0])\n\tif(_can_make_signal_assertions(sp.object, sp.signal_name)):\n\t\tif(_signal_watcher.did_emit(sp.object, sp.signal_name)):\n\t\t\t_pass(disp)\n\t\telse:\n\t\t\t_fail(_get_fail_msg_including_emitted_signals(disp, sp.object))\n\n\n## This works opposite of `assert_signal_emitted`.  This will fail if the object\n## is not being watched or if the object does not have the signal.\n## [br][br]\n## [b]Signatures:[/b][br]\n## - assert_signal_not_emitted([param p1]:Signal, [param p2]: text <optional>)[br]\n## - assert_signal_not_emitted([param p1]:object, [param p2]:signal-name, [param p3]: text <optional>)\n## [br][br]\n## [b]Examples:[/b]\n## [codeblock]\n##    class SignalObject:\n##        signal some_signal\n##        signal other_signal\n##\n##    func test_assert_signal_not_emitted():\n##        var obj = SignalObject.new()\n##\n##        watch_signals(obj)\n##        obj.emit_signal('some_signal')\n##\n##        # Passing\n##        assert_signal_not_emitted(obj, 'other_signal')\n##        assert_signal_not_emitted(obj.other_signal)\n##\n##        # Failing\n##        # Fails with specific message that the object does not have the signal\n##        assert_signal_not_emitted(obj, 'signal_does_not_exist')\n##        # Fails because the object passed is not being watched\n##        assert_signal_not_emitted(SignalObject.new(), 'some_signal')\n##        # Fails because the signal was emitted\n##        assert_signal_not_emitted(obj, 'some_signal')\n## [/codeblock]\nfunc assert_signal_not_emitted(p1, p2='', p3=''):\n\tvar sp := SignalAssertParameters.new(p1, p2, p3)\n\tvar disp = str('Expected object ', _str(sp.object), ' to NOT emit signal [', sp.signal_name, ']:  ', sp.others[0])\n\tif(_can_make_signal_assertions(sp.object, sp.signal_name)):\n\t\tif(_signal_watcher.did_emit(sp.object, sp.signal_name)):\n\t\t\t_fail(disp)\n\t\telse:\n\t\t\t_pass(disp)\n\n\n## Asserts that a signal was fired with the specified parameters.  The expected\n## parameters should be passed in as an array.  An optional index can be passed\n## when a signal has fired more than once.  The default is to retrieve the most\n## recent emission of the signal.\n## [br]\n## This will fail with specific messages if the object is not being watched or\n## the object does not have the specified signal\n## [br][br]\n## [b]Signatures:[/b][br]\n## - assert_signal_emitted_with_parameters([param p1]:Signal, [param p2]:expected-parameters, [param p3]: index <optional>)[br]\n## - assert_signal_emitted_with_parameters([param p1]:object, [param p2]:signal-name, [param p3]:expected-parameters, [param p4]: index <optional>)\n## [br][br]\n## [b]Examples:[/b]\n## [codeblock]\n## class SignalObject:\n##     signal some_signal\n##     signal other_signal\n##\n## func test_assert_signal_emitted_with_parameters():\n##     var obj = SignalObject.new()\n##\n##     watch_signals(obj)\n##     # emit the signal 3 times to illustrate how the index works in\n##     # assert_signal_emitted_with_parameters\n##     obj.emit_signal('some_signal', 1, 2, 3)\n##     obj.emit_signal('some_signal', 'a', 'b', 'c')\n##     obj.emit_signal('some_signal', 'one', 'two', 'three')\n##\n##     # Passing\n##     # Passes b/c the default parameters to check are the last emission of\n##     # the signal\n##     assert_signal_emitted_with_parameters(obj, 'some_signal', ['one', 'two', 'three'])\n##     assert_signal_emitted_with_parameters(obj.some_signal, ['one', 'two', 'three'])\n##\n##     # Passes because the parameters match the specified emission based on index.\n##     assert_signal_emitted_with_parameters(obj, 'some_signal', [1, 2, 3], 0)\n##     assert_signal_emitted_with_parameters(obj.some_signal, [1, 2, 3], 0)\n##\n##     # Failing\n##     # Fails with specific message that the object does not have the signal\n##     assert_signal_emitted_with_parameters(obj, 'signal_does_not_exist', [])\n##     # Fails because the object passed is not being watched\n##     assert_signal_emitted_with_parameters(SignalObject.new(), 'some_signal', [])\n##     # Fails because parameters do not match latest emission\n##     assert_signal_emitted_with_parameters(obj, 'some_signal', [1, 2, 3])\n##     # Fails because the parameters for the specified index do not match\n##     assert_signal_emitted_with_parameters(obj, 'some_signal', [1, 2, 3], 1)\n## [/codeblock]\nfunc assert_signal_emitted_with_parameters(p1, p2, p3=-1, p4=-1):\n\tvar sp := SignalAssertParameters.new(p1, p2, p3, p4)\n\tvar parameters = sp.others[0]\n\tvar index = sp.others[1]\n\n\tif(typeof(parameters) != TYPE_ARRAY):\n\t\t_lgr.error(\"The expected parameters must be wrapped in an array, you passed:  \" + _str(parameters))\n\t\t_fail(\"Bad Parameters\")\n\t\treturn\n\n\tvar disp = str('Expected object ', _str(sp.object), ' to emit signal [', sp.signal_name, '] with parameters ', parameters, ', got ')\n\tif(_can_make_signal_assertions(sp.object, sp.signal_name)):\n\t\tif(_signal_watcher.did_emit(sp.object, sp.signal_name)):\n\t\t\tvar parms_got = _signal_watcher.get_signal_parameters(sp.object, sp.signal_name, index)\n\t\t\tvar diff_result = _compare.deep(parameters, parms_got)\n\t\t\tif(diff_result.are_equal):\n\t\t\t\t_pass(str(disp, parms_got))\n\t\t\telse:\n\t\t\t\t_fail(str('Expected object ', _str(sp.object), ' to emit signal [', sp.signal_name, '] with parameters ', diff_result.summarize()))\n\t\telse:\n\t\t\tvar text = str('Object ', sp.object, ' did not emit signal [', sp.signal_name, ']')\n\t\t\t_fail(_get_fail_msg_including_emitted_signals(text, sp.object))\n\n\n## Asserts that a signal fired a specific number of times.\n## [br][br]\n## [b]Signatures:[/b][br]\n## - assert_signal_emit_count([param p1]:Signal, [param p2]:expected-count, [param p3]: text <optional>)[br]\n## - assert_signal_emit_count([param p1]:object, [param p2]:signal-name, [param p3]:expected-count, [param p4]: text <optional>)\n## [br][br]\n## [b]Examples:[/b]\n## [codeblock]\n## class SignalObject:\n##     signal some_signal\n##     signal other_signal\n##\n##\n## func test_assert_signal_emit_count():\n##     var obj_a = SignalObject.new()\n##     var obj_b = SignalObject.new()\n##\n##     watch_signals(obj_a)\n##     watch_signals(obj_b)\n##\n##     obj_a.emit_signal('some_signal')\n##     obj_a.emit_signal('some_signal')\n##\n##     obj_b.emit_signal('some_signal')\n##     obj_b.emit_signal('other_signal')\n##\n##     # Passing\n##     assert_signal_emit_count(obj_a, 'some_signal', 2, 'passes')\n##     assert_signal_emit_count(obj_a.some_signal, 2, 'passes')\n##\n##     assert_signal_emit_count(obj_a, 'other_signal', 0)\n##     assert_signal_emit_count(obj_a.other_signal, 0)\n##\n##     assert_signal_emit_count(obj_b, 'other_signal', 1)\n##\n##     # Failing\n##     # Fails with specific message that the object does not have the signal\n##     assert_signal_emit_count(obj_a, 'signal_does_not_exist', 99)\n##     # Fails because the object passed is not being watched\n##     assert_signal_emit_count(SignalObject.new(), 'some_signal', 99)\n##     # The following fail for obvious reasons\n##     assert_signal_emit_count(obj_a, 'some_signal', 0)\n##     assert_signal_emit_count(obj_b, 'other_signal', 283)\n## [/codeblock]\nfunc assert_signal_emit_count(p1, p2, p3=0, p4=\"\"):\n\tvar sp := SignalAssertParameters.new(p1, p2, p3, p4)\n\tvar times = sp.others[0]\n\tvar text = sp.others[1]\n\n\tif(_can_make_signal_assertions(sp.object, sp.signal_name)):\n\t\tvar count = _signal_watcher.get_emit_count(sp.object, sp.signal_name)\n\t\tvar disp = str('Expected the signal [', sp.signal_name, '] emit count of [', count, '] to equal [', times, ']: ', text)\n\t\tif(count== times):\n\t\t\t_pass(disp)\n\t\telse:\n\t\t\t_fail(_get_fail_msg_including_emitted_signals(disp, sp.object))\n\n\n## Asserts the passed in object has a signal with the specified name.  It\n## should be noted that all the asserts that verify a signal was/wasn't emitted\n## will first check that the object has the signal being asserted against.  If\n## it does not, a specific failure message will be given.  This means you can\n## usually skip the step of specifically verifying that the object has a signal\n## and move on to making sure it emits the signal correctly.\n## [codeblock]\n##    class SignalObject:\n##        signal some_signal\n##        signal other_signal\n##\n##    func test_assert_has_signal():\n##        var obj = SignalObject.new()\n##\n##        ## Passing\n##        assert_has_signal(obj, 'some_signal')\n##        assert_has_signal(obj, 'other_signal')\n##\n##        ## Failing\n##        assert_has_signal(obj, 'not_a real SIGNAL')\n##        assert_has_signal(obj, 'yea, this one doesnt exist either')\n##        # Fails because the signal is not a user signal.  Node2D does have the\n##        # specified signal but it can't be checked this way.  It could be watched\n##        # and asserted that it fired though.\n##        assert_has_signal(Node2D.new(), 'exit_tree')\n## [/codeblock]\nfunc assert_has_signal(object, signal_name, text=\"\"):\n\tvar disp = str('Expected object ', _str(object), ' to have signal [', signal_name, ']:  ', text)\n\tif(_signal_watcher.does_object_have_signal(object, signal_name)):\n\t\t_pass(disp)\n\telse:\n\t\t_fail(disp)\n\n\n## Asserts that [param object] extends [param a_class].  object must be an instance of an\n## object.  It cannot be any of the built in classes like Array or Int or Float.\n## [param a_class] must be a class, it can be loaded via load, a GDNative class such as\n## Node or Label or anything else.\n## [codeblock]\n##    # Passing\n##    assert_is(Node2D.new(), Node2D)\n##    assert_is(Label.new(), CanvasItem)\n##    assert_is(SubClass.new(), BaseClass)\n##    # Since this is a test script that inherits from test.gd, so\n##    # this passes.  It's not obvious w/o seeing the whole script\n##    # so I'm telling you.  You'll just have to trust me.\n##    assert_is(self, load('res://addons/gut/test.gd'))\n##\n##    var Gut = load('res://addons/gut/gut.gd')\n##    var a_gut = Gut.new()\n##    assert_is(a_gut, Gut)\n##\n##    # Failing\n##    assert_is(Node2D.new(), Node2D.new())\n##    assert_is(BaseClass.new(), SubClass)\n##    assert_is('a', 'b')\n##    assert_is([], Node)\n## [/codeblock]\nfunc assert_is(object, a_class, text=''):\n\tvar disp  = ''#var disp = str('Expected [', _str(object), '] to be type of [', a_class, ']: ', text)\n\tvar bad_param_2 = 'Parameter 2 must be a Class (like Node2D or Label).  You passed '\n\n\tif(typeof(object) != TYPE_OBJECT):\n\t\t_fail(str('Parameter 1 must be an instance of an object.  You passed:  ', _str(object)))\n\telif(typeof(a_class) != TYPE_OBJECT):\n\t\t_fail(str(bad_param_2, _str(a_class)))\n\telse:\n\t\tvar a_str = _str(a_class)\n\t\tdisp = str('Expected [', _str(object), '] to extend [', a_str, ']: ', text)\n\t\tif(!GutUtils.is_native_class(a_class) and !GutUtils.is_gdscript(a_class)):\n\t\t\t_fail(str(bad_param_2, a_str))\n\t\telse:\n\t\t\tif(is_instance_of(object, a_class)):\n\t\t\t\t_pass(disp)\n\t\t\telse:\n\t\t\t\t_fail(disp)\n\n\n## Asserts that [param object] is the the [param type] specified.  [param type]\n## should be one of the Godot [code]TYPE_[/code] constants.\n## [codeblock]\n##    # Passing\n##    var c = Color(1, 1, 1, 1)\n##    gr.test.assert_typeof(c, TYPE_COLOR)\n##    assert_pass(gr.test)\n##\n##    # Failing\n##    gr.test.assert_typeof('some string', TYPE_INT)\n##    assert_fail(gr.test)\n## [/codeblock]\nfunc assert_typeof(object, type, text=''):\n\tvar disp = str('Expected [typeof(', object, ') = ')\n\tdisp += _get_typeof_string(typeof(object))\n\tdisp += '] to equal ['\n\tdisp += _get_typeof_string(type) +  ']'\n\tdisp += '.  ' + text\n\tif(typeof(object) == type):\n\t\t_pass(disp)\n\telse:\n\t\t_fail(disp)\n\n\n## The inverse of [method assert_typeof]\nfunc assert_not_typeof(object, type, text=''):\n\tvar disp = str('Expected [typeof(', object, ') = ')\n\tdisp += _get_typeof_string(typeof(object))\n\tdisp += '] to not equal ['\n\tdisp += _get_typeof_string(type) +  ']'\n\tdisp += '.  ' + text\n\tif(typeof(object) != type):\n\t\t_pass(disp)\n\telse:\n\t\t_fail(disp)\n\n\n## Assert that `text` contains `search`.  Can perform case insensitive search\n## by passing false for `match_case`.\n## [codeblock]\n##    # Passing\n##    assert_string_contains('abc 123', 'a')\n##    assert_string_contains('abc 123', 'BC', false)\n##    assert_string_contains('abc 123', '3')\n##\n##    # Failing\n##    assert_string_contains('abc 123', 'A')\n##    assert_string_contains('abc 123', 'BC')\n##    assert_string_contains('abc 123', '012')\n## [/codeblock]\nfunc assert_string_contains(text, search, match_case=true):\n\tconst empty_search = 'Expected text and search strings to be non-empty. You passed %s and %s.'\n\tconst non_strings = 'Expected text and search to both be strings.  You passed %s and %s.'\n\tvar disp = 'Expected \\'%s\\' to contain \\'%s\\', match_case=%s' % [text, search, match_case]\n\tif(typeof(text) != TYPE_STRING or typeof(search) != TYPE_STRING):\n\t\t_fail(non_strings % [_str(text), _str(search)])\n\telif(text == '' or search == ''):\n\t\t_fail(empty_search % [_str(text), _str(search)])\n\telif(match_case):\n\t\tif(text.find(search) == -1):\n\t\t\t_fail(disp)\n\t\telse:\n\t\t\t_pass(disp)\n\telse:\n\t\tif(text.to_lower().find(search.to_lower()) == -1):\n\t\t\t_fail(disp)\n\t\telse:\n\t\t\t_pass(disp)\n\n\n## Assert that text starts with search.  Can perform case insensitive check\n## by passing false for match_case\n## [codeblock]\n##    # Passing\n##    assert_string_starts_with('abc 123', 'a')\n##    assert_string_starts_with('abc 123', 'ABC', false)\n##    assert_string_starts_with('abc 123', 'abc 123')\n##\n##    ## Failing\n##    assert_string_starts_with('abc 123', 'z')\n##    assert_string_starts_with('abc 123', 'ABC')\n##    assert_string_starts_with('abc 123', 'abc 1234')\n## [/codeblock]\nfunc assert_string_starts_with(text, search, match_case=true):\n\tvar empty_search = 'Expected text and search strings to be non-empty. You passed \\'%s\\' and \\'%s\\'.'\n\tvar disp = 'Expected \\'%s\\' to start with \\'%s\\', match_case=%s' % [text, search, match_case]\n\tif(text == '' or search == ''):\n\t\t_fail(empty_search % [text, search])\n\telif(match_case):\n\t\tif(text.find(search) == 0):\n\t\t\t_pass(disp)\n\t\telse:\n\t\t\t_fail(disp)\n\telse:\n\t\tif(text.to_lower().find(search.to_lower()) == 0):\n\t\t\t_pass(disp)\n\t\telse:\n\t\t\t_fail(disp)\n\n\n## Assert that [param text] ends with [param search].  Can perform case insensitive check by passing false for [param match_case]\n## [codeblock]\n##    ## Passing\n##    assert_string_ends_with('abc 123', '123')\n##    assert_string_ends_with('abc 123', 'C 123', false)\n##    assert_string_ends_with('abc 123', 'abc 123')\n##\n##    ## Failing\n##    assert_string_ends_with('abc 123', '1234')\n##    assert_string_ends_with('abc 123', 'C 123')\n##    assert_string_ends_with('abc 123', 'nope')\n## [/codeblock]\nfunc assert_string_ends_with(text, search, match_case=true):\n\tvar empty_search = 'Expected text and search strings to be non-empty. You passed \\'%s\\' and \\'%s\\'.'\n\tvar disp = 'Expected \\'%s\\' to end with \\'%s\\', match_case=%s' % [text, search, match_case]\n\tvar required_index = len(text) - len(search)\n\tif(text == '' or search == ''):\n\t\t_fail(empty_search % [text, search])\n\telif(match_case):\n\t\tif(text.find(search) == required_index):\n\t\t\t_pass(disp)\n\t\telse:\n\t\t\t_fail(disp)\n\telse:\n\t\tif(text.to_lower().find(search.to_lower()) == required_index):\n\t\t\t_pass(disp)\n\t\telse:\n\t\t\t_fail(disp)\n\n\n# ------------------------------------------------------------------------------\n## Assert that a method was called on an instance of a doubled class.  If\n## parameters are supplied then the params passed in when called must match.\n##\n## Can be called with a Callabe instead of specifying the object, method_name,\n## and parameters.  The Callable's object must be a double.  Bound arguments\n## will be used to match calls based on values passed to the method.\n## [br]\n## See also: [wiki]Doubles[/wiki], [wiki]Spies[/wiki]\n## [br][br]\n## [b]Examples[/b]\n## [codeblock]\n##    var my_double = double(Foobar).new()\n##    ...\n##    assert_called(my_double, 'foo')\n##    assert_called(my_double.foo)\n##    assert_called(my_double, 'foo', [1, 2, 3])\n##    assert_called(my_double.foo.bind(1, 2, 3))\n## [/codeblock]\nfunc assert_called(inst, method_name=null, parameters=null):\n\n\tif(_fail_if_parameters_not_array(parameters)):\n\t\treturn\n\n\tvar converted = _convert_spy_args(inst, method_name, parameters)\n\tif(converted.invalid_message != 'ok'):\n\t\tfail_test(converted.invalid_message)\n\t\treturn\n\n\tvar disp = str('Expected [',converted.method_name,'] to have been called on ',_str(converted.object))\n\tif(converted.arguments != null):\n\t\tdisp += str(' with parameters ', converted.arguments)\n\n\tif(_fail_if_not_double_or_does_not_have_method(converted.object, converted.method_name) == OK):\n\t\tif(gut.get_spy().was_called(\n\t\t\tconverted.object, converted.method_name, converted.arguments)):\n\t\t\t_pass(disp)\n\t\telse:\n\t\t\t_fail(str(disp, \"\\n\", _get_desc_of_calls_to_instance(converted.object)))\n\n\n# ------------------------------------------------------------------------------\n## Assert that a method was not called on an instance of a doubled class.  If\n## parameters are specified then this will only fail if it finds a call that was\n## sent matching parameters.\n##\n## Can be called with a Callabe instead of specifying the object, method_name,\n## and parameters.  The Callable's object must be a double.  Bound arguments\n## will be used to match calls based on values passed to the method.\n## [br]\n## See also: [wiki]Doubles[/wiki], [wiki]Spies[/wiki]\n## [br][br]\n## [b]Examples[/b]\n## [codeblock]\n##    assert_not_called(my_double, 'foo')\n##    assert_not_called(my_double.foo)\n##    assert_not_called(my_double, 'foo', [1, 2, 3])\n##    assert_not_called(my_double.foo.bind(1, 2, 3))\n## [/codeblock]\nfunc assert_not_called(inst, method_name=null, parameters=null):\n\n\tif(_fail_if_parameters_not_array(parameters)):\n\t\treturn\n\n\tvar converted = _convert_spy_args(inst, method_name, parameters)\n\tif(converted.invalid_message != 'ok'):\n\t\tfail_test(converted.invalid_message)\n\t\treturn\n\n\tvar disp = str('Expected [', converted.method_name, '] to NOT have been called on ', _str(converted.object))\n\n\tif(_fail_if_not_double_or_does_not_have_method(converted.object, converted.method_name) == OK):\n\t\tif(gut.get_spy().was_called(\n\t\t\tconverted.object, converted.method_name, converted.arguments)):\n\t\t\tif(converted.arguments != null):\n\t\t\t\tdisp += str(' with parameters ', converted.arguments)\n\t\t\t_fail(str(disp, \"\\n\", _get_desc_of_calls_to_instance(converted.object)))\n\t\telse:\n\t\t\t_pass(disp)\n\n\n## Asserts the the method of a double was called an expected number of times.\n## If any arguments are bound to the callable then only calls with matching\n## arguments will be counted.\n## [br]\n## See also: [wiki]Doubles[/wiki], [wiki]Spies[/wiki]\n## [br][br]\n## [b]Examples[/b]\n## [codeblock]\n##    # assert foo was called on my_double 5 times\n##    assert_called_count(my_double.foo, 5)\n##    # assert foo, with parameters [1,2,3], was called on my_double 4 times.\n##    assert_called_count(my_double.foo.bind(1, 2, 3), 4)\n## [/codeblock]\nfunc assert_called_count(callable : Callable, expected_count : int):\n\tvar converted = _convert_spy_args(callable, null, null)\n\tvar count = gut.get_spy().call_count(converted.object, converted.method_name, converted.arguments)\n\n\tvar param_text = ''\n\tif(callable.get_bound_arguments_count() > 0):\n\t\tparam_text = ' with parameters ' + str(callable.get_bound_arguments())\n\tvar disp = 'Expected [%s] on %s to be called [%s] times%s.  It was called [%s] times.'\n\tdisp = disp % [converted.method_name, _str(converted.object), expected_count, param_text, count]\n\n\n\tif(_fail_if_not_double_or_does_not_have_method(converted.object, converted.method_name) == OK):\n\t\tif(count == expected_count):\n\t\t\t_pass(disp)\n\t\telse:\n\t\t\t_fail(str(disp, \"\\n\", _get_desc_of_calls_to_instance(converted.object)))\n\n\n## Asserts the passed in value is null\nfunc assert_null(got, text=''):\n\tvar disp = str('Expected [', _str(got), '] to be NULL:  ', text)\n\tif(got == null):\n\t\t_pass(disp)\n\telse:\n\t\t_fail(disp)\n\n\n## Asserts the passed in value is not null.\nfunc assert_not_null(got, text=''):\n\tvar disp = str('Expected [', _str(got), '] to be anything but NULL:  ', text)\n\tif(got == null):\n\t\t_fail(disp)\n\telse:\n\t\t_pass(disp)\n\n\n## Asserts that the passed in object has been freed.  This assertion requires\n## that  you pass in some text in the form of a title since, if the object is\n## freed, we won't have anything to convert to a string to put in the output\n## statement.\n## [br]\n## [b]Note[/b] that this currently does not detect if a node has been queued free.\n## [codeblock]\n##    var obj = Node.new()\n##    obj.free()\n##    test.assert_freed(obj, \"New Node\")\n## [/codeblock]\nfunc assert_freed(obj, title='something'):\n\tvar disp = title\n\tif(is_instance_valid(obj)):\n\t\tdisp = _strutils.type2str(obj) + title\n\tassert_true(not is_instance_valid(obj), \"Expected [%s] to be freed\" % disp)\n\n\n## The inverse of [method assert_freed]\nfunc assert_not_freed(obj, title='something'):\n\tvar disp = title\n\tif(is_instance_valid(obj)):\n\t\tdisp = _strutils.type2str(obj) + title\n\tassert_true(is_instance_valid(obj), \"Expected [%s] to not be freed\" % disp)\n\n\n## This method will assert that no orphaned nodes have been introduced by the\n## test when the assert is executed.  See the [wiki]Memory-Management[/wiki]\n## page for more information.\nfunc assert_no_new_orphans(text=''):\n\tvar orphan_ids = gut.get_current_test_orphans()\n\tvar count = orphan_ids.size()\n\tvar msg = ''\n\tif(text != ''):\n\t\tmsg = ':  ' + text\n\t# Note that get_counter will return -1 if the counter does not exist.  This\n\t# can happen with a misplaced assert_no_new_orphans.  Checking for > 0\n\t# ensures this will not cause some weird failure.\n\tif(count > 0):\n\t\tmsg += str(\"\\n\", _strutils.indent_text(gut.get_orphan_counter().get_orphan_list_text(orphan_ids), 1, '    '))\n\t\t_fail(str('Expected no orphans, but found ', count, msg))\n\telse:\n\t\t_pass('No new orphans found.' + msg)\n\n\n## @ignore\nfunc assert_set_property(obj, property_name, new_value, expected_value):\n\tpending(\"this hasn't been implemented yet\")\n\n\n## @ignore\nfunc assert_readonly_property(obj, property_name, new_value, expected_value):\n\tpending(\"this hasn't been implemented yet\")\n\n\n## Assumes backing varible with be _<property_name>.  This will perform all the\n## asserts of assert_property.  Then this will set the value through the setter\n## and check the backing variable value.  It will then reset throught the setter\n## and set the backing variable and check the getter.\nfunc assert_property_with_backing_variable(obj, property_name, default_value, new_value, backed_by_name=null):\n\tvar setter_name = str('@', property_name, '_setter')\n\tvar getter_name = str('@', property_name, '_getter')\n\tvar backing_name = GutUtils.nvl(backed_by_name, str('_', property_name))\n\tvar pre_fail_count = get_fail_count()\n\n\tvar props = obj.get_property_list()\n\tvar found = false\n\tvar idx = 0\n\twhile(idx < props.size() and !found):\n\t\tfound = props[idx].name == backing_name\n\t\tidx += 1\n\n\tassert_true(found, str(obj, ' has ', backing_name, ' variable.'))\n\tassert_true(obj.has_method(setter_name), str('There should be a setter for ', property_name))\n\tassert_true(obj.has_method(getter_name), str('There should be a getter for ', property_name))\n\n\tif(pre_fail_count == get_fail_count()):\n\t\tvar call_setter = Callable(obj, setter_name)\n\t\tvar call_getter = Callable(obj, getter_name)\n\n\t\tassert_eq(obj.get(backing_name), default_value, str('Variable ', backing_name, ' has default value.'))\n\t\tassert_eq(call_getter.call(), default_value, 'Getter returns default value.')\n\t\tcall_setter.call(new_value)\n\t\tassert_eq(call_getter.call(), new_value, 'Getter returns value from Setter.')\n\t\tassert_eq(obj.get(backing_name), new_value, str('Variable ', backing_name, ' was set'))\n\n\t_warn_for_public_accessors(obj, property_name)\n\n\n## This will verify that the method has a setter and getter for the property.\n## It will then use the getter to check the default.  Then use the\n## setter with new_value and verify the getter returns the same value.\nfunc assert_property(obj, property_name, default_value, new_value) -> void:\n\tvar pre_fail_count = get_fail_count()\n\n\tvar setter_name = str('@', property_name, '_setter')\n\tvar getter_name = str('@', property_name, '_getter')\n\n\tif(typeof(obj) != TYPE_OBJECT):\n\t\t_fail(str(_str(obj), ' is not an object'))\n\t\treturn\n\n\tassert_has_method(obj, setter_name)\n\tassert_has_method(obj, getter_name)\n\n\tif(pre_fail_count == get_fail_count()):\n\t\tvar call_setter = Callable(obj, setter_name)\n\t\tvar call_getter = Callable(obj, getter_name)\n\n\t\tassert_eq(call_getter.call(), default_value, 'Default value')\n\t\tcall_setter.call(new_value)\n\t\tassert_eq(call_getter.call(), new_value, 'Getter gets Setter value')\n\n\t_warn_for_public_accessors(obj, property_name)\n\n\n## Performs a deep comparison between two arrays or dictionaries and asserts\n## they are equal.  If they are not equal then a formatted list of differences\n## are displayed.  See [wiki]Comparing-Things[/wiki] for more information.\nfunc assert_eq_deep(v1, v2):\n\tvar result = compare_deep(v1, v2)\n\tif(result.are_equal):\n\t\t_pass(result.get_short_summary())\n\telse:\n\t\t_fail(result.summary)\n\n\n## Performs a deep comparison of two arrays or dictionaries and asserts they\n## are not equal.  See [wiki]Comparing-Things[/wiki] for more information.\nfunc assert_ne_deep(v1, v2):\n\tvar result = compare_deep(v1, v2)\n\tif(!result.are_equal):\n\t\t_pass(result.get_short_summary())\n\telse:\n\t\t_fail(result.get_short_summary())\n\n\n## Assert v1 and v2 are the same using [code]is_same[/code].  See @GlobalScope.is_same.\nfunc assert_same(v1, v2, text=''):\n\tvar disp = \"[\" + _str(v1) + \"] expected to be same as  [\" + _str(v2) + \"]:  \" + text\n\tif(is_same(v1, v2)):\n\t\t_pass(disp)\n\telse:\n\t\t_fail(disp)\n\n\n## Assert using v1 and v2 are not the same using [code]is_same[/code].  See @GlobalScope.is_same.\nfunc assert_not_same(v1, v2, text=''):\n\tvar disp = \"[\" + _str(v1) + \"] expected to not be same as  [\" + _str(v2) + \"]:  \" + text\n\tif(is_same(v1, v2)):\n\t\t_fail(disp)\n\telse:\n\t\t_pass(disp)\n\n# ----------------\n#endregion\n#region Error Detection\n# ----------------\nvar _error_type_check_methods = {\n\t\"push_error\": \"is_push_error\",\n\t\"engine\": \"is_engine_error\",\n}\n\n# smells like GutTrackedError needs some more constants but I'm not ready to\n# make them yet\nfunc _is_error_of_type(err, error_type_name):\n\treturn err.call(_error_type_check_methods[error_type_name])\n\n\nfunc _assert_error_count(count, error_type_name, msg):\n\tvar consumed_count = 0\n\tvar errors = gut.error_tracker.get_errors_for_test()\n\tvar found = []\n\tvar disp = msg\n\n\tfor err in errors:\n\t\tif(_is_error_of_type(err, error_type_name)):\n\t\t\tif(consumed_count < count):\n\t\t\t\terr.handled = true\n\t\t\t\tconsumed_count += 1\n\t\t\tfound.append(err)\n\n\tif(disp != ''):\n\t\tdisp = str(':  ', disp)\n\telse:\n\t\tdisp = '.'\n\tdisp = str(\"Expected \", count, \" \", error_type_name, \" errors.  Got \", found.size(), disp)\n\tif(found.size() == count):\n\t\t_pass(disp)\n\t\tif(!_lgr.is_type_enabled(_lgr.types.passed)):\n\t\t\t_lgr.expected_error(msg)\n\telse:\n\t\t_fail(disp)\n\n\nfunc _assert_error_text(text, error_type_name, msg):\n\tvar consumed_count = 0\n\tvar errors = gut.error_tracker.get_errors_for_test()\n\tvar found = []\n\tvar disp = msg\n\n\tfor err in errors:\n\t\tif(_is_error_of_type(err, error_type_name) and err.contains_text(text)):\n\t\t\tif(consumed_count == 0):\n\t\t\t\terr.handled = true\n\t\t\t\tconsumed_count += 1\n\t\t\tfound.append(err)\n\n\tdisp = str(\"Expected \", error_type_name, \" error containing '\", text, \"'.  \", msg)\n\tif(consumed_count == 1):\n\t\t_pass(disp)\n\t\tif(!_lgr.is_type_enabled(_lgr.types.passed)):\n\t\t\t_lgr.expected_error(disp)\n\telse:\n\t\t_fail(disp)\n\n\n## Get all the errors in the test up to this point.  Each error is an instance\n## of [GutTrackedError]. Setting the [member GutTrackedError.handled] [code]handled[/code] property of\n## an element in the array will prevent it from causing a test to fail.\n## [br][br]\n## This method allows you to inspect the details of any errors that occured and\n## decide if it's the error you are expecting or not.\n## [br][br]\n## [codeblock]\n## func divide_them(a, b):\n##     return a / b\n##\n## func test_with_script_error():\n##     divide_them('one', 44)\n##     push_error('this is a push error')\n##     var errs = get_errors()\n##     assert_eq(errs.size(), 2, 'expected error count')\n##\n##     # Maybe inspect some properties of the errors here.\n##\n##     # Mark all the errors as handled.\n##     for e in errs:\n##         e.handled = true\n## [/codeblock]\n## See [GutTrackedError], [wiki]Error-Tracking[/wiki].\nfunc get_errors()->Array:\n\treturn gut.error_tracker.get_errors_for_test()\n\n\n## Asserts that a number of engine or a single engine error continating\n## (case insensitive) text has occurred.  If the expected error(s) are\n## found then this assert will pass and the test will not fail from an\n## unexpected push_error.\n## [br][br]\n## This assert will pass/fail even if push_errors are not configured to cause\n## a test failure.  This will not prevent the error from showing up in output.\n## [br][br]\n## [codeblock]\n## func divide_them(a, b):\n##     return a / b\n##\n## func test_asserting_engine_error_count():\n##     divide_them('one', 44)\n##     assert_engine_error(1, \"expecing a script error\")\n##\n## func test_asserting_engine_error_text():\n##     divide_them('word', 91)\n##     assert_engine_error('invalid operands')\n##\n## func test_asserting_multipe_engine_error_texts():\n##     divide_them('foo', Node)\n##     divide_them(1729, 0)\n##     assert_engine_error('Division by zero')\n##     assert_engine_error('invalid operands')\n## [/codeblock]\n## See [wiki]Error-Tracking[/wiki].\nfunc assert_engine_error(count_or_text, msg=''):\n\tvar t = typeof(count_or_text)\n\tif(t == TYPE_INT or t == TYPE_FLOAT):\n\t\t_assert_error_count(count_or_text, \"engine\", msg)\n\telif(t == TYPE_STRING):\n\t\t_assert_error_text(count_or_text, 'engine', msg)\n\telse:\n\t\t_fail(str(\"Unexpected input:  \", count_or_text))\n\n\n## Asserts that a number of push_errors or a single push error continating\n## (case insensitive) text has occurred.  If the expected error(s) are\n## found then this assert will pass and the test will not fail from an\n## unexpected push_error.\n## [br][br]\n## This assert will pass/fail even if push_errors are not configured to cause\n## a test failure.  This will not prevent the error from showing up in output.\n## [codeblock]\n## func test_with_push_error():\n##     push_error(\"This is an error\")\n##     assert_push_error(1, 'This test should have caused a push_error)\n##\n## func test_push_error_text():\n##     push_error(\"SpecialText\")\n##     assert_push_error(\"CIALtex\")\n##\n## func test_push_error_multiple_texts():\n##     push_error(\"Error One\")\n##     push_error(\"Expception two\")\n##     assert_push_error(\"one\")\n##     assert_push_error(\"two\")\n##\n## [/codeblock]\n## See [wiki]Error-Tracking[/wiki].\nfunc assert_push_error(count_or_text, msg=''):\n\tvar t = typeof(count_or_text)\n\tif(t == TYPE_INT or t == TYPE_FLOAT):\n\t\t_assert_error_count(count_or_text, \"push_error\", msg)\n\telif(t == TYPE_STRING):\n\t\t_assert_error_text(count_or_text, 'push_error', msg)\n\telse:\n\t\t_fail(str(\"Unexpected input:  \", count_or_text))\n\n\n# ----------------\n#endregion\n#region Await Helpers\n# ----------------\n\n\n## Use with await to wait an amount of time in seconds.  The optional message\n## will be printed when the await starts.[br]\n## See [wiki]Awaiting[/wiki]\nfunc wait_seconds(time, msg=''):\n\t_awaiter.wait_seconds(time)\n\treturn _awaiter.timeout\n\n\n## Use with await to wait for a signal to be emitted or a maximum amount of\n## time.  Returns true if the signal was emitted, false if not.[br]\n## See [wiki]Awaiting[/wiki]\nfunc wait_for_signal(sig : Signal, max_time, msg=''):\n\twatch_signals(sig.get_object())\n\t_awaiter.wait_for_signal(sig, max_time, msg)\n\tawait _awaiter.timeout\n\treturn !_awaiter.did_last_wait_timeout\n\n\n## @deprecated\n## Use wait_physics_frames or wait_process_frames\n## See [wiki]Awaiting[/wiki].\nfunc wait_frames(frames : int, msg=''):\n\t_lgr.deprecated(\"wait_frames has been replaced with wait_physics_frames which is counted in _physics_process.  \" +\n\t\t\"wait_process_frames has also been added which is counted in _process.\")\n\treturn wait_physics_frames(frames, msg)\n\n\n## This returns a signal that is emitted after [param x] physics frames have\n## elpased.  You can await this method directly to pause execution for [param x]\n## physics frames.  The frames are counted prior to _physics_process being called\n## on any node (when [signal SceneTree.physics_frame] is emitted).  This means the\n## signal is emitted after [param x] frames and just before the x + 1 frame starts.\n## [codeblock]\n## await wait_physics_frames(10)\n## [/codeblock]\n## See [wiki]Awaiting[/wiki]\nfunc wait_physics_frames(x :int , msg=''):\n\tif(x <= 0):\n\t\tvar text = str('wait_physics_frames:  frames must be > 0, you passed  ', x, '.  1 frames waited.')\n\t\t_lgr.error(text)\n\t\tx = 1\n\n\t_awaiter.wait_physics_frames(x, msg)\n\treturn _awaiter.timeout\n\n\n## Alias for [method GutTest.wait_process_frames]\nfunc wait_idle_frames(x : int, msg=''):\n\treturn wait_process_frames(x, msg)\n\n\n## This returns a signal that is emitted after [param x] process/idle frames have\n## elpased.  You can await this method directly to pause execution for [param x]\n## process/idle frames.  The frames are counted prior to _process being called\n## on any node (when [signal SceneTree.process_frame] is emitted).  This means the\n## signal is emitted after [param x] frames and just before the x + 1 frame starts.\n## [codeblock]\n## await wait_process_frames(10)\n## # wait_idle_frames is an alias of wait_process_frames\n## await wait_idle_frames(10)\n## [/codeblock]\n## See [wiki]Awaiting[/wiki]\nfunc wait_process_frames(x : int, msg=''):\n\tif(x <= 0):\n\t\tvar text = str('wait_process_frames:  frames must be > 0, you passed  ', x, '.  1 frames waited.')\n\t\t_lgr.error(text)\n\t\tx = 1\n\n\t_awaiter.wait_process_frames(x, msg)\n\treturn _awaiter.timeout\n\n\n## Use with await to wait for [param callable] to return the boolean value\n## [code]true[/code] or a maximum amount of time.  All values that are not the\n## boolean value [code]true[/code] are ignored.  [param callable] is called\n## every [code]_physics_process[/code] tick unless an optional time between\n## calls is specified.[br]\n## [param p3] can be the optional message or an amount of time to wait between calls.[br]\n## [param p4] is the optional message if you have specified an amount of time to\n## wait between calls.[br]\n## Returns [code]true[/code] if [param callable] returned true before the timeout, false if not.\n##[br]\n##[codeblock]\n## var foo = 1\n## func test_example():\n##     var foo_func = func():\n##         foo += 1\n##         return foo == 10\n##     foo = 1\n##     wait_until(foo_func, 5, 'optional message')\n##     # or give it a time between\n##     foo = 1\n##     wait_until(foo_func, 5, 1,\n##         'this will timeout because we call it every second and are waiting a max of 10 seconds')\n##\n##[/codeblock]\n## See also [method wait_while][br]\n## See [wiki]Awaiting[/wiki]\nfunc wait_until(callable, max_time, p3='', p4=''):\n\tvar time_between = 0.0\n\tvar message = p4\n\tif(typeof(p3) != TYPE_STRING):\n\t\ttime_between = p3\n\telse:\n\t\tmessage = p3\n\n\t_awaiter.wait_until(callable, max_time, time_between, message)\n\tawait _awaiter.timeout\n\treturn !_awaiter.did_last_wait_timeout\n\n\n## This is the inverse of [method wait_until].  This will continue to wait while\n## [param callable] returns the boolean value [code]true[/code].  If [b]ANY[/b]\n## other value is is returned then the wait will end.\n## Returns [code]true[/code] if [param callable] returned a value other than\n## [code]true[/code] before the timeout, [code]false[/code] if not.\n##[codeblock]\n## var foo = 1\n## func test_example():\n##     var foo_func = func():\n##         foo += 1\n##         if(foo < 10):\n##             return true\n##         else:\n##             return 'this is not a boolean'\n##     foo = 1\n##     wait_while(foo_func, 5, 'optional message')\n##     # or give it a time between\n##     foo = 1\n##     wait_while(foo_func, 5, 1,\n##         'this will timeout because we call it every second and are waiting a max of 10 seconds')\n##\n##[/codeblock]\n## See [wiki]Awaiting[/wiki]\nfunc wait_while(callable, max_time, p3='', p4=''):\n\tvar time_between = 0.0\n\tvar message = p4\n\tif(typeof(p3) != TYPE_STRING):\n\t\ttime_between = p3\n\telse:\n\t\tmessage = p3\n\n\t_awaiter.wait_while(callable, max_time, time_between, message)\n\tawait _awaiter.timeout\n\treturn !_awaiter.did_last_wait_timeout\n\n\n\n## Returns whether the last wait_* method timed out.  This is always true if\n## the last method was wait_xxx_frames or wait_seconds.  It will be false when\n## using wait_for_signal and wait_until if the timeout occurs before what\n## is being waited on.  The wait_* methods return this value so you should be\n## able to avoid calling this directly, but you can.\nfunc did_wait_timeout():\n\treturn _awaiter.did_last_wait_timeout\n\n# ----------------\n#endregion\n#region Summary Data\n# ----------------\n\n## @internal\nfunc get_summary():\n\treturn _summary\n\n\n## Returns the number of failing asserts in this script at the time this\n## method was called.  Call in [method after_all] to get total count for script.\nfunc get_fail_count():\n\treturn _summary.failed\n\n\n## Returns the number of passing asserts in this script at the time this method\n## was called.  Call in [method after_all] to get total count for script.\nfunc get_pass_count():\n\treturn _summary.passed\n\n\n## Returns the number of pending tests in this script at the time this method\n## was called.  Call in [method after_all] to get total count for script.\nfunc get_pending_count():\n\treturn _summary.pending\n\n\n## Returns the total number of asserts this script has made as of the time of\n## this was called.  Call in [method after_all] to get total count for script.\nfunc get_assert_count():\n\treturn _summary.asserts\n\n\n# Convert the _summary dictionary into text\n## @internal\nfunc get_summary_text():\n\tvar to_return = get_script().get_path() + \"\\n\"\n\tto_return += str('  ', _summary.passed, ' of ', _summary.asserts, ' passed.')\n\tif(_summary.pending > 0):\n\t\tto_return += str(\"\\n  \", _summary.pending, ' pending')\n\tif(_summary.failed > 0):\n\t\tto_return += str(\"\\n  \", _summary.failed, ' failed.')\n\treturn to_return\n\n\n# ----------------\n#endregion\n#region Double Methods\n# ----------------\n\n\n## Create a Double of [param thing].  [param thing] should be a Class, script,\n## or scene.  See [wiki]Doubles[/wiki]\nfunc double(thing, double_strat=null, not_used_anymore=null):\n\tif(!_are_double_parameters_valid(thing, double_strat, not_used_anymore)):\n\t\treturn null\n\n\treturn _smart_double(thing, double_strat, false)\n\n\n## Create a Partial Double of [param thing].  [param thing] should be a Class,\n## script, or scene.  See [wiki]Partial-Doubles[/wiki]\nfunc partial_double(thing, double_strat=null, not_used_anymore=null):\n\tif(!_are_double_parameters_valid(thing, double_strat, not_used_anymore)):\n\t\treturn null\n\n\treturn _smart_double(thing, double_strat, true)\n\n\n## @internal\nfunc double_singleton(singleton_name):\n\treturn null\n\t# var to_return = null\n\t# if(_validate_singleton_name(singleton_name)):\n\t# \tto_return = gut.get_doubler().double_singleton(singleton_name)\n\t# return to_return\n\n\n## @internal\nfunc partial_double_singleton(singleton_name):\n\treturn null\n\t# var to_return = null\n\t# if(_validate_singleton_name(singleton_name)):\n\t# \tto_return = gut.get_doubler().partial_double_singleton(singleton_name)\n\t# return to_return\n\n\n## This was implemented to allow the doubling of classes with static methods.\n## There might be other valid use cases for this method, but you should always\n## try stubbing before using this method.  Using\n## [code]stub(my_double, 'method').to_call_super()[/code] or  creating a\n## [method partial_double] works for any other known scenario.  You cannot stub\n## or spy on methods passed to [code skip-lint]ignore_method_when_doubling[/code].\nfunc ignore_method_when_doubling(thing, method_name):\n\tif(typeof(thing) == TYPE_STRING):\n\t\t_lgr.error('ignore_method_when_doubling no longer supports paths to scripts or scenes.  Load them and pass them instead.')\n\t\treturn\n\n\tvar r = thing\n\tif(thing is PackedScene):\n\t\tr = GutUtils.get_scene_script_object(thing)\n\n\tgut.get_doubler().add_ignored_method(r, method_name)\n\n\n## Stub something.  See [wiki]Stubbing[/wiki] for detailed information about stubbing.\nfunc stub(thing, p2=null, p3=null):\n\tvar method_name = p2\n\tvar subpath = null\n\n\tif(p3 != null):\n\t\tsubpath = p2\n\t\tmethod_name = p3\n\n\tif(GutUtils.is_instance(thing) and !GutUtils.is_double(thing)):\n\t\t_lgr.error(str(\"An instance of a Double was expected, you passed:  \", _str(thing)))\n\t\treturn GutUtils.StubParams.new()\n\n\tvar sp = null\n\tif(typeof(thing) == TYPE_CALLABLE):\n\t\tif(p2 != null or p3 != null):\n\t\t\t_lgr.error(\"Only one parameter expected when using a callable.\")\n\t\tsp = GutUtils.StubParams.new(thing)\n\telse:\n\t\tsp = GutUtils.StubParams.new(thing, method_name, subpath)\n\n\tif(GutUtils.is_instance(sp.stub_target)):\n\t\tvar msg = _get_bad_method_message(sp.stub_target, sp.stub_method, 'stub')\n\t\tif(msg != ''):\n\t\t\t_lgr.error(msg)\n\t\t\treturn GutUtils.StubParams.new()\n\n\tsp.logger = _lgr\n\tgut.get_stubber().add_stub(sp)\n\treturn sp\n\n\n# ----------------\n#endregion\n#region Memory Mgmt\n# ----------------\n\n\n## Marks whatever is passed in to be freed after the test finishes.  It also\n## returns what is passed in so you can save a line of code.\n##   var thing = autofree(Thing.new())\nfunc autofree(thing):\n\tgut.get_autofree().add_free(thing)\n\treturn thing\n\n\n## Works the same as autofree except queue_free will be called on the object\n## instead.  This also imparts a brief pause after the test finishes so that\n## the queued object has time to free.\nfunc autoqfree(thing):\n\tgut.get_autofree().add_queue_free(thing)\n\treturn thing\n\n\n## The same as autofree but it also adds the object as a child of the test.\nfunc add_child_autofree(node, legible_unique_name = false):\n\tgut.get_autofree().add_free(node)\n\t# Explicitly calling super here b/c add_child MIGHT change and I don't want\n\t# a bug sneaking its way in here.\n\tsuper.add_child(node, legible_unique_name)\n\treturn node\n\n\n## The same as autoqfree but it also adds the object as a child of the test.\nfunc add_child_autoqfree(node, legible_unique_name=false):\n\tgut.get_autofree().add_queue_free(node)\n\t# Explicitly calling super here b/c add_child MIGHT change and I don't want\n\t# a bug sneaking its way in here.\n\tsuper.add_child(node, legible_unique_name)\n\treturn node\n\n\n# ----------------\n#endregion\n#region Deprecated/Removed\n# ----------------\n\n\n## REMOVED\n## @ignore\nfunc compare_shallow(v1, v2, max_differences=null):\n\t_fail('compare_shallow has been removed.  Use compare_deep or just compare using == instead.')\n\t_lgr.error('compare_shallow has been removed.  Use compare_deep or just compare using == instead.')\n\treturn null\n\n\n## REMOVED\n## @ignore\nfunc assert_eq_shallow(v1, v2):\n\t_fail('assert_eq_shallow has been removed.  Use assert_eq/assert_same/assert_eq_deep')\n\n\n## REMOVED\n## @ignore\nfunc assert_ne_shallow(v1, v2):\n\t_fail('assert_eq_shallow has been removed.  Use assert_eq/assert_same/assert_eq_deep')\n\n\n## @deprecated: use wait_seconds\nfunc yield_for(time, msg=''):\n\t_lgr.deprecated('yield_for', 'wait_seconds')\n\treturn wait_seconds(time, msg)\n\n\n## @deprecated: use wait_for_signal\nfunc yield_to(obj, signal_name, max_wait, msg=''):\n\t_lgr.deprecated('yield_to', 'wait_for_signal')\n\treturn await wait_for_signal(Signal(obj, signal_name), max_wait, msg)\n\n\n## @deprecated: use wait_frames\nfunc yield_frames(frames, msg=''):\n\t_lgr.deprecated(\"yield_frames\", \"wait_frames\")\n\treturn wait_frames(frames, msg)\n\n\n## @deprecated: no longer supported.  Use double\nfunc double_scene(path, strategy=null):\n\t_lgr.deprecated('test.double_scene has been removed.', 'double')\n\treturn null\n\n\n## @deprecated: no longer supported.  Use double\nfunc double_script(path, strategy=null):\n\t_lgr.deprecated('test.double_script has been removed.', 'double')\n\treturn null\n\n\t# var override_strat = GutUtils.nvl(strategy, gut.get_doubler().get_strategy())\n\t# return gut.get_doubler().double(path, override_strat)\n\n\n## @deprecated: no longer supported.  Use register_inner_classes + double\nfunc double_inner(path, subpath, strategy=null):\n\t_lgr.deprecated('double_inner should not be used.  Use register_inner_classes and double instead.', 'double')\n\treturn null\n\n\tvar override_strat = GutUtils.nvl(strategy, gut.get_doubler().get_strategy())\n\treturn gut.get_doubler().double_inner(path, subpath, override_strat)\n\n\n## @deprecated:  Use [method assert_called_count] instead.\nfunc assert_call_count(inst, method_name, expected_count, parameters=null):\n\tgut.logger.deprecated('This has been replaced with assert_called_count which accepts a Callable with optional bound arguments.')\n\tvar callable = Callable.create(inst, method_name)\n\tif(parameters != null):\n\t\tcallable = callable.bindv(parameters)\n\tassert_called_count(callable, expected_count)\n\n\n## @deprecated: no longer supported.\nfunc assert_setget(\n\tinstance, name_property,\n\tconst_or_setter = null, getter=\"__not_set__\"):\n\t_lgr.deprecated('assert_property')\n\t_fail('assert_setget has been removed.  Use assert_property, assert_set_property, assert_readonly_property instead.')\n\n\n# ----------------\n#endregion\n\n\n\n# ##############################################################################\n#(G)odot (U)nit (T)est class\n#\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n# View readme for usage details.\n#\n# Version - see gut.gd\n# ##############################################################################\n# Class that all test scripts must extend.`\n#\n# This provides all the asserts and other testing features.  Test scripts are\n# run by the Gut class in gut.gd\n# ##############################################################################\n"
  },
  {
    "path": "demo/addons/gut/test.gd.uid",
    "content": "uid://cnup2nbj45r4s\n"
  },
  {
    "path": "demo/addons/gut/test_collector.gd",
    "content": "# ------------------------------------------------------------------------------\n# This class handles calling out to the test parser and maintaining an array of\n# collected_script.gd.  This is used for both calling the tests and tracking\n# the results of each script and test's execution.\n#\n# This also handles exporting and importing tests.\n# ------------------------------------------------------------------------------\nvar CollectedScript = GutUtils.CollectedScript\nvar CollectedTest = GutUtils.CollectedTest\n\nvar _test_prefix = 'test_'\nvar _test_class_prefix = 'Test'\n\nvar _lgr = GutUtils.get_logger()\n\n\n# Array of CollectedScripts.\nvar scripts = []\n\n\nfunc _does_inherit_from_test(thing):\n\tvar base_script = thing.get_base_script()\n\tvar to_return = false\n\tif(base_script != null):\n\t\tvar base_path = base_script.get_path()\n\t\tif(base_path == 'res://addons/gut/test.gd'):\n\t\t\tto_return = true\n\t\telse:\n\t\t\tto_return = _does_inherit_from_test(base_script)\n\treturn to_return\n\n\nfunc _populate_tests(test_script):\n\tvar script =  test_script.load_script()\n\tif(script == null):\n\t\tprint('  !!! ', test_script.path, ' could not be loaded')\n\t\treturn false\n\n\ttest_script.is_loaded = true\n\tvar methods = script.get_script_method_list()\n\tfor i in range(methods.size()):\n\t\tvar name = methods[i]['name']\n\t\tif(name.begins_with(_test_prefix)):\n\t\t\tvar t = CollectedTest.new()\n\t\t\tt.name = name\n\t\t\tt.arg_count = methods[i]['args'].size()\n\t\t\ttest_script.tests.append(t)\n\t\t\tt.collected_script = weakref(test_script)\n\n\nfunc _get_inner_test_class_names(loaded):\n\tvar inner_classes = []\n\tvar const_map = loaded.get_script_constant_map()\n\tfor key in const_map:\n\t\tvar thing = const_map[key]\n\t\tif(GutUtils.is_gdscript(thing)):\n\t\t\tif(key.begins_with(_test_class_prefix)):\n\t\t\t\tif(_does_inherit_from_test(thing)):\n\t\t\t\t\tinner_classes.append(key)\n\t\t\t\telse:\n\t\t\t\t\t_lgr.warn(str('Ignoring Inner Class ', key,\n\t\t\t\t\t\t' because it does not extend GutTest'))\n\n\t\t\t# This could go deeper and find inner classes within inner classes\n\t\t\t# but requires more experimentation.  Right now I'm keeping it at\n\t\t\t# one level since that is what the previous version did and there\n\t\t\t# has been no demand for deeper nesting.\n\t\t\t# _populate_inner_test_classes(thing)\n\treturn inner_classes\n\n\nfunc _parse_script(test_script):\n\tvar inner_classes = []\n\tvar scripts_found = []\n\n\tvar loaded = GutUtils.WarningsManager.load_script_using_custom_warnings(\n\t\ttest_script.path,\n\t\tGutUtils.warnings_when_loading_test_scripts)\n\n\tif(_does_inherit_from_test(loaded)):\n\t\t_populate_tests(test_script)\n\t\tscripts_found.append(test_script.path)\n\t\tinner_classes = _get_inner_test_class_names(loaded)\n\telse:\n\t\treturn []\n\n\tfor i in range(inner_classes.size()):\n\t\tvar loaded_inner = loaded.get(inner_classes[i])\n\t\tif(_does_inherit_from_test(loaded_inner)):\n\t\t\tvar ts = CollectedScript.new(_lgr)\n\t\t\tts.path = test_script.path\n\t\t\tts.inner_class_name = inner_classes[i]\n\t\t\t_populate_tests(ts)\n\t\t\tscripts.append(ts)\n\t\t\tscripts_found.append(test_script.path + '[' + inner_classes[i] +']')\n\n\treturn scripts_found\n\n\n# -----------------\n# Public\n# -----------------\nfunc add_script(path):\n\t# SHORTCIRCUIT\n\tif(has_script(path)):\n\t\treturn []\n\n\t# SHORTCIRCUIT\n\tif(!FileAccess.file_exists(path)):\n\t\t# This check was added so tests could create dynmaic scripts and add\n\t\t# them to be run through gut.  This helps cut down on creating test\n\t\t# scripts to be used in test/resources.\n\t\tif(ResourceLoader.has_cached(path)):\n\t\t\t_lgr.debug(\"Using cached version of \" + path)\n\t\telse:\n\t\t\t_lgr.error('Could not find script:  ' + path)\n\t\t\treturn\n\n\tvar ts = CollectedScript.new(_lgr)\n\tts.path = path\n\t# Append right away because if we don't test_doubler.gd.TestInitParameters\n\t# will HARD crash.  I couldn't figure out what was causing the issue but\n\t# appending right away, and then removing if it's not valid seems to fix\n\t# things.  It might have to do with the ordering of the test classes in\n\t# the test collecter.  I'm not really sure.\n\tscripts.append(ts)\n\tvar parse_results = _parse_script(ts)\n\n\tif(parse_results.find(path) == -1):\n\t\t_lgr.warn(str('Ignoring script ', path, ' because it does not extend GutTest'))\n\t\tscripts.remove_at(scripts.find(ts))\n\n\treturn parse_results\n\n\nfunc clear():\n\tscripts.clear()\n\n\nfunc has_script(path):\n\tvar found = false\n\tvar idx = 0\n\twhile(idx < scripts.size() and !found):\n\t\tif(scripts[idx].get_full_name() == path):\n\t\t\tfound = true\n\t\telse:\n\t\t\tidx += 1\n\treturn found\n\n\nfunc export_tests(path):\n\tvar success = true\n\tvar f = ConfigFile.new()\n\tfor i in range(scripts.size()):\n\t\tscripts[i].export_to(f, str('CollectedScript-', i))\n\tvar result = f.save(path)\n\tif(result != OK):\n\t\t_lgr.error(str('Could not save exported tests to [', path, '].  Error code:  ', result))\n\t\tsuccess = false\n\treturn success\n\n\nfunc import_tests(path):\n\tvar success = false\n\tvar f = ConfigFile.new()\n\tvar result = f.load(path)\n\tif(result != OK):\n\t\t_lgr.error(str('Could not load exported tests from [', path, '].  Error code:  ', result))\n\telse:\n\t\tvar sections = f.get_sections()\n\t\tfor key in sections:\n\t\t\tvar ts = CollectedScript.new(_lgr)\n\t\t\tts.import_from(f, key)\n\t\t\t_populate_tests(ts)\n\t\t\tscripts.append(ts)\n\t\tsuccess = true\n\treturn success\n\n\nfunc get_script_named(name):\n\treturn GutUtils.search_array(scripts, 'get_filename_and_inner', name)\n\n\nfunc get_test_named(script_name, test_name):\n\tvar s = get_script_named(script_name)\n\tif(s != null):\n\t\treturn s.get_test_named(test_name)\n\telse:\n\t\treturn null\n\n\nfunc to_s():\n\tvar to_return = ''\n\tfor i in range(scripts.size()):\n\t\tto_return += scripts[i].to_s() + \"\\n\"\n\treturn to_return\n\n# ---------------------\n# Accessors\n# ---------------------\nfunc get_logger():\n\treturn _lgr\n\n\nfunc set_logger(logger):\n\t_lgr = logger\n\n\nfunc get_test_prefix():\n\treturn _test_prefix\n\n\nfunc set_test_prefix(test_prefix):\n\t_test_prefix = test_prefix\n\n\nfunc get_test_class_prefix():\n\treturn _test_class_prefix\n\n\nfunc set_test_class_prefix(test_class_prefix):\n\t_test_class_prefix = test_class_prefix\n\n\nfunc get_scripts():\n\treturn scripts\n\n\nfunc get_ran_test_count():\n\tvar count = 0\n\tfor s in scripts:\n\t\tcount += s.get_ran_test_count()\n\treturn count\n\n\nfunc get_ran_script_count():\n\tvar count = 0\n\tfor s in scripts:\n\t\tif(s.was_run):\n\t\t\tcount += 1\n\treturn count\n\nfunc get_test_count():\n\tvar count = 0\n\tfor s in scripts:\n\t\tcount += s.tests.size()\n\treturn count\n\n\nfunc get_assert_count():\n\tvar count = 0\n\tfor s in scripts:\n\t\tcount += s.get_assert_count()\n\treturn count\n\n\nfunc get_pass_count():\n\tvar count = 0\n\tfor s in scripts:\n\t\tcount += s.get_pass_count()\n\treturn count\n\n\nfunc get_fail_count():\n\tvar count = 0\n\tfor s in scripts:\n\t\tcount += s.get_fail_count()\n\treturn count\n\n\nfunc get_pending_count():\n\tvar count = 0\n\tfor s in scripts:\n\t\tcount += s.get_pending_count()\n\treturn count\n\n"
  },
  {
    "path": "demo/addons/gut/test_collector.gd.uid",
    "content": "uid://cly8ws3u71jk5\n"
  },
  {
    "path": "demo/addons/gut/thing_counter.gd",
    "content": "var things = {}\n\nfunc get_unique_count():\n\treturn things.size()\n\n\nfunc add_thing_to_count(thing):\n\tif(!things.has(thing)):\n\t\tthings[thing] = 0\n\n\nfunc add(thing):\n\tif(things.has(thing)):\n\t\tthings[thing] += 1\n\telse:\n\t\tthings[thing] = 1\n\n\nfunc has(thing):\n\treturn things.has(thing)\n\n\nfunc count(thing):\n\tvar to_return = 0\n\tif(things.has(thing)):\n\t\tto_return = things[thing]\n\treturn to_return\n\n\nfunc sum():\n\tvar to_return = 0\n\tfor key in things:\n\t\tto_return += things[key]\n\treturn to_return\n\n\nfunc to_s():\n\tvar to_return = \"\"\n\tfor key in things:\n\t\tto_return += str(key, \":  \", things[key], \"\\n\")\n\tto_return += str(\"sum: \", sum())\n\treturn to_return\n\n\nfunc get_max_count():\n\tvar max_val = null\n\tfor key in things:\n\t\tif(max_val == null or things[key] > max_val):\n\t\t\tmax_val = things[key]\n\treturn max_val\n\n\nfunc add_array_items(array):\n\tfor i in range(array.size()):\n\t\tadd(array[i])\n"
  },
  {
    "path": "demo/addons/gut/thing_counter.gd.uid",
    "content": "uid://8evk5cwvo2nu\n"
  },
  {
    "path": "demo/addons/gut/utils.gd",
    "content": "@tool\nclass_name GutUtils\nextends Object\n\nconst GUT_METADATA = '__gutdbl'\n\n# Note, these cannot change since places are checking for TYPE_INT to determine\n# how to process parameters.\nenum DOUBLE_STRATEGY{\n\tINCLUDE_NATIVE,\n\tSCRIPT_ONLY,\n}\n\nenum DIFF {\n\tDEEP,\n\tSIMPLE\n}\n\nconst TEST_STATUSES = {\n\tNO_ASSERTS = 'no asserts',\n\tSKIPPED = 'skipped',\n\tNOT_RUN = 'not run',\n\tPENDING = 'pending',\n\t# These two got the \"ed\" b/c pass is a reserved word and I could not\n\t# think of better words.\n\tFAILED = 'fail',\n\tPASSED = 'pass'\n}\n\nconst DOUBLE_TEMPLATES = {\n\tFUNCTION = 'res://addons/gut/double_templates/function_template.txt',\n\tINIT = 'res://addons/gut/double_templates/init_template.txt',\n\tSCRIPT = 'res://addons/gut/double_templates/script_template.txt',\n}\n\nconst NOTHING := '__NOTHING__'\nconst NO_TEST := 'NONE'\nconst GUT_ERROR_TYPE = 999\n\nenum TREAT_AS {\n\tNOTHING,\n\tFAILURE,\n}\n\n\n## This dictionary defaults to all the native classes that we cannot call new\n## on.  It is further populated during a run so that we only have to create\n## a new instance once to get the class name string.\nstatic var gdscript_native_class_names_by_type = {\n\tTween:\"Tween\"\n}\n\n\nstatic var GutScene = load('res://addons/gut/GutScene.tscn')\nstatic var LazyLoader = load('res://addons/gut/lazy_loader.gd')\nstatic var VersionNumbers = load(\"res://addons/gut/version_numbers.gd\")\nstatic var WarningsManager = load(\"res://addons/gut/warnings_manager.gd\")\nstatic var EditorGlobals = load(\"res://addons/gut/gui/editor_globals.gd\")\nstatic var RunExternallyScene = load(\"res://addons/gut/gui/RunExternally.tscn\")\n\n# --------------------------------\n# Lazy loaded scripts.  These scripts are lazy loaded so that they can be\n# declared, but will not load when this script is loaded.  This gives us a\n# window at the start of a run to adjust warning levels prior to loading\n# everything.\n# --------------------------------\nstatic var AutoFree = LazyLoader.new('res://addons/gut/autofree.gd'):\n\tget: return AutoFree.get_loaded()\n\tset(val): pass\nstatic var Awaiter = LazyLoader.new('res://addons/gut/awaiter.gd'):\n\tget: return Awaiter.get_loaded()\n\tset(val): pass\nstatic var Comparator = LazyLoader.new('res://addons/gut/comparator.gd'):\n\tget: return Comparator.get_loaded()\n\tset(val): pass\nstatic var CollectedTest = LazyLoader.new('res://addons/gut/collected_test.gd'):\n\tget: return CollectedTest.get_loaded()\n\tset(val): pass\nstatic var CollectedScript = LazyLoader.new('res://addons/gut/collected_script.gd'):\n\tget: return CollectedScript.get_loaded()\n\tset(val): pass\nstatic var CompareResult = LazyLoader.new('res://addons/gut/compare_result.gd'):\n\tget: return CompareResult.get_loaded()\n\tset(val): pass\nstatic var DiffFormatter = LazyLoader.new(\"res://addons/gut/diff_formatter.gd\"):\n\tget: return DiffFormatter.get_loaded()\n\tset(val): pass\nstatic var DiffTool = LazyLoader.new('res://addons/gut/diff_tool.gd'):\n\tget: return DiffTool.get_loaded()\n\tset(val): pass\nstatic var DoubleTools = LazyLoader.new(\"res://addons/gut/double_tools.gd\"):\n\tget: return DoubleTools.get_loader()\n\tset(val): pass\nstatic var Doubler = LazyLoader.new('res://addons/gut/doubler.gd'):\n\tget: return Doubler.get_loaded()\n\tset(val): pass\nstatic var DynamicGdScript = LazyLoader.new(\"res://addons/gut/dynamic_gdscript.gd\") :\n\tget: return DynamicGdScript.get_loaded()\n\tset(val): pass\nstatic var Gut = LazyLoader.new('res://addons/gut/gut.gd'):\n\tget: return Gut.get_loaded()\n\tset(val): pass\nstatic var GutConfig = LazyLoader.new('res://addons/gut/gut_config.gd'):\n\tget: return GutConfig.get_loaded()\n\tset(val): pass\nstatic var GutFonts = LazyLoader.new(\"res://addons/gut/gut_fonts.gd\"):\n\tget: return GutFonts.get_loaded()\n\tset(val): pass\nstatic var HookScript = LazyLoader.new('res://addons/gut/hook_script.gd'):\n\tget: return HookScript.get_loaded()\n\tset(val): pass\nstatic var InnerClassRegistry = LazyLoader.new('res://addons/gut/inner_class_registry.gd'):\n\tget: return InnerClassRegistry.get_loaded()\n\tset(val): pass\nstatic var InputFactory = LazyLoader.new(\"res://addons/gut/input_factory.gd\"):\n\tget: return InputFactory.get_loaded()\n\tset(val): pass\nstatic var InputSender = LazyLoader.new(\"res://addons/gut/input_sender.gd\"):\n\tget: return InputSender.get_loaded()\n\tset(val): pass\nstatic var JunitXmlExport = LazyLoader.new('res://addons/gut/junit_xml_export.gd'):\n\tget: return JunitXmlExport.get_loaded()\n\tset(val): pass\nstatic var GutLogger = LazyLoader.new('res://addons/gut/logger.gd') : # everything should use get_logger\n\tget: return GutLogger.get_loaded()\n\tset(val): pass\nstatic var MethodMaker = LazyLoader.new('res://addons/gut/method_maker.gd'):\n\tget: return MethodMaker.get_loaded()\n\tset(val): pass\nstatic var OneToMany = LazyLoader.new('res://addons/gut/one_to_many.gd'):\n\tget: return OneToMany.get_loaded()\n\tset(val): pass\nstatic var OptionMaker = LazyLoader.new('res://addons/gut/gui/option_maker.gd'):\n\tget: return OptionMaker.get_loaded()\n\tset(val): pass\nstatic var OrphanCounter = LazyLoader.new('res://addons/gut/orphan_counter.gd'):\n\tget: return OrphanCounter.get_loaded()\n\tset(val): pass\nstatic var ParameterFactory = LazyLoader.new('res://addons/gut/parameter_factory.gd'):\n\tget: return ParameterFactory.get_loaded()\n\tset(val): pass\nstatic var ParameterHandler = LazyLoader.new('res://addons/gut/parameter_handler.gd'):\n\tget: return ParameterHandler.get_loaded()\n\tset(val): pass\nstatic var Printers = LazyLoader.new('res://addons/gut/printers.gd'):\n\tget: return Printers.get_loaded()\n\tset(val): pass\nstatic var ResultExporter = LazyLoader.new('res://addons/gut/result_exporter.gd'):\n\tget: return ResultExporter.get_loaded()\n\tset(val): pass\nstatic var ScriptCollector = LazyLoader.new('res://addons/gut/script_parser.gd'):\n\tget: return ScriptCollector.get_loaded()\n\tset(val): pass\nstatic var SignalWatcher = LazyLoader.new('res://addons/gut/signal_watcher.gd'):\n\tget: return SignalWatcher.get_loaded()\n\tset(val): pass\nstatic var Spy = LazyLoader.new('res://addons/gut/spy.gd'):\n\tget: return Spy.get_loaded()\n\tset(val): pass\nstatic var Strutils = LazyLoader.new('res://addons/gut/strutils.gd'):\n\tget: return Strutils.get_loaded()\n\tset(val): pass\nstatic var Stubber = LazyLoader.new('res://addons/gut/stubber.gd'):\n\tget: return Stubber.get_loaded()\n\tset(val): pass\nstatic var StubParams = LazyLoader.new('res://addons/gut/stub_params.gd'):\n\tget: return StubParams.get_loaded()\n\tset(val): pass\nstatic var Summary = LazyLoader.new('res://addons/gut/summary.gd'):\n\tget: return Summary.get_loaded()\n\tset(val): pass\nstatic var Test = LazyLoader.new('res://addons/gut/test.gd'):\n\tget: return Test.get_loaded()\n\tset(val): pass\nstatic var TestCollector = LazyLoader.new('res://addons/gut/test_collector.gd'):\n\tget: return TestCollector.get_loaded()\n\tset(val): pass\nstatic var ThingCounter = LazyLoader.new('res://addons/gut/thing_counter.gd'):\n\tget: return ThingCounter.get_loaded()\n\tset(val): pass\n# --------------------------------\n\nstatic var gut_fonts = GutFonts.new()\nstatic var avail_fonts = gut_fonts.get_font_names()\n\nstatic var version_numbers = VersionNumbers.new(\n\t# gut_versrion (source of truth)\n\t'9.5.0',\n\t# required_godot_version\n\t'4.5'\n)\n\n\nstatic var warnings_at_start := { # WarningsManager dictionary\n\texclude_addons = true\n}\n\nstatic var warnings_when_loading_test_scripts := { # WarningsManager dictionary\n\tenable = false\n}\n\n\n# ------------------------------------------------------------------------------\n# Everything should get a logger through this.\n#\n# When running in test mode this will always return a new logger so that errors\n# are not caused by getting bad warn/error/etc counts.\n# ------------------------------------------------------------------------------\nstatic var _lgr = null\nstatic func get_logger():\n\tif(_lgr == null):\n\t\t_lgr = GutLogger.new()\n\treturn _lgr\n\nstatic var _error_tracker = null\nstatic func get_error_tracker():\n\tif(_error_tracker == null):\n\t\t_error_tracker = GutErrorTracker.new()\n\treturn _error_tracker\n\n\nstatic var _dyn_gdscript = DynamicGdScript.new()\nstatic func create_script_from_source(source, override_path=null):\n\tvar are_warnings_enabled = WarningsManager.are_warnings_enabled()\n\tWarningsManager.enable_warnings(false)\n\n\tvar DynamicScript = _dyn_gdscript.create_script_from_source(source, override_path)\n\tif(typeof(DynamicScript) == TYPE_INT):\n\t\tvar l = get_logger()\n\t\tl.error(str('Could not create script from source.  Error:  ', DynamicScript))\n\t\tl.info(str(\"Source Code:\\n\", add_line_numbers(source)))\n\n\tWarningsManager.enable_warnings(are_warnings_enabled)\n\n\treturn DynamicScript\n\n\n# Get the EditorInterface instance without having to make a direct reference to\n# it.  This allows for testing to be done on editor scripts that require it\n# without having the parser error when you refer to it when not in the editor.\nstatic func get_editor_interface():\n\tif(Engine.is_editor_hint()):\n\t\tvar src = \"\"\"\n\t\tfunc get_it():\n\t\t\treturn EditorInterface\n\t\t\"\"\"\n\t\tvar s = create_script_from_source(src).new()\n\t\treturn s.get_it()\n\telse:\n\t\treturn null\n\n\n\nstatic func godot_version_string():\n\treturn version_numbers.make_godot_version_string()\n\n\nstatic func is_godot_version(expected):\n\treturn VersionNumbers.VerNumTools.is_godot_version_eq(expected)\n\n\nstatic func is_godot_version_gte(expected):\n\treturn VersionNumbers.VerNumTools.is_godot_version_gte(expected)\n\n\nconst INSTALL_OK_TEXT = 'Everything checks out'\nstatic func make_install_check_text(template_paths=DOUBLE_TEMPLATES, ver_nums=version_numbers):\n\tvar text = INSTALL_OK_TEXT\n\tif(!FileAccess.file_exists(template_paths.FUNCTION) or\n\t\t!FileAccess.file_exists(template_paths.INIT) or\n\t\t!FileAccess.file_exists(template_paths.SCRIPT)):\n\n\t\ttext = 'One or more GUT template files are missing.  If this is an exported project, you must include *.txt files in the export to run GUT.  If it is not an exported project then reinstall GUT.'\n\telif(!ver_nums.is_godot_version_valid()):\n\t\ttext = ver_nums.get_bad_version_text()\n\n\treturn text\n\n\nstatic func is_install_valid(template_paths=DOUBLE_TEMPLATES, ver_nums=version_numbers):\n\treturn make_install_check_text(template_paths, ver_nums) == INSTALL_OK_TEXT\n\n\n# ------------------------------------------------------------------------------\n# Gets the root node without having to be in the tree and pushing out an error\n# if we don't have a main loop ready to go yet.\n# ------------------------------------------------------------------------------\n# static func get_root_node():\n# \tvar main_loop = Engine.get_main_loop()\n# \tif(main_loop != null):\n# \t\treturn main_loop.root\n# \telse:\n# \t\tpush_error('No Main Loop Yet')\n# \t\treturn null\n\n\n# ------------------------------------------------------------------------------\n# Gets the value from an enum.\n# - If passed an integer value as a string it will convert it to an int and\n# \tprocesses the int value.\n# - If the value is a float then it is converted to an int and then processes\n#\tthe int value\n# - If the value is an int, or was converted to an int, then the enum is checked\n#\tto see if it contains the value, if so then the value is returned.\n#\tOtherwise the default is returned.\n# - If the value is a string then it is uppercased and all spaces are replaced\n#\twith underscores.  It then checks to see if enum contains a key of that\n#\tname.  If so then the value for that key is returned, otherwise the default\n#\tis returned.\n#\n# This description is longer than the code, you should have just read the code\n# and the tests.\n# ------------------------------------------------------------------------------\nstatic func get_enum_value(thing, e, default=null):\n\tvar to_return = default\n\n\tif(typeof(thing) == TYPE_STRING and str(thing.to_int()) == thing):\n\t\tthing = thing.to_int()\n\telif(typeof(thing) == TYPE_FLOAT):\n\t\tthing = int(thing)\n\n\tif(typeof(thing) == TYPE_STRING):\n\t\tvar converted = thing.to_upper().replace(' ', '_')\n\t\tif(e.keys().has(converted)):\n\t\t\tto_return = e[converted]\n\telse:\n\t\tif(e.values().has(thing)):\n\t\t\tto_return = thing\n\n\treturn to_return\n\n\n# ------------------------------------------------------------------------------\n# return if_null if value is null otherwise return value\n# ------------------------------------------------------------------------------\nstatic func nvl(value, if_null):\n\tif(value == null):\n\t\treturn if_null\n\telse:\n\t\treturn value\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nstatic func pretty_print(dict, indent = '  '):\n\tprint(JSON.stringify(dict, indent))\n\n\n# ------------------------------------------------------------------------------\n# ------------------------------------------------------------------------------\nstatic func print_properties(props, thing, print_all_meta=false):\n\tfor i in range(props.size()):\n\t\tvar prop_name = props[i].name\n\t\tvar prop_value = thing.get(props[i].name)\n\t\tvar print_value = str(prop_value)\n\t\tif(print_value.length() > 100):\n\t\t\tprint_value = print_value.substr(0, 97) + '...'\n\t\telif(print_value == ''):\n\t\t\tprint_value = 'EMPTY'\n\n\t\tprint(prop_name, ' = ', print_value)\n\t\tif(print_all_meta):\n\t\t\tprint('  ', props[i])\n\n\nstatic func print_method_list(thing):\n\tfor entry in thing.get_method_list():\n\t\tprint(\"* \", entry.name)\n\n\n# ------------------------------------------------------------------------------\n# Gets the value of the node_property 'script' from a PackedScene's root node.\n# This does not assume the location of the root node in the PackedScene's node\n# list.  This also does not assume the index of the 'script' node property in\n# a nodes's property list.\n# ------------------------------------------------------------------------------\nstatic func get_scene_script_object(scene):\n\tvar state = scene.get_state()\n\tvar to_return = null\n\tvar root_node_path = NodePath(\".\")\n\tvar node_idx = 0\n\n\twhile(node_idx < state.get_node_count() and to_return == null):\n\t\tif(state.get_node_path(node_idx) == root_node_path):\n\t\t\tfor i in range(state.get_node_property_count(node_idx)):\n\t\t\t\tif(state.get_node_property_name(node_idx, i) == 'script'):\n\t\t\t\t\tto_return = state.get_node_property_value(node_idx, i)\n\n\t\tnode_idx += 1\n\n\treturn to_return\n\n\n# ------------------------------------------------------------------------------\n# returns true if the object has been freed, false if not\n#\n# From what i've read, the weakref approach should work.  It seems to work most\n# of the time but sometimes it does not catch it.  The str comparison seems to\n# fill in the gaps.  I've not seen any errors after adding that check.\n# ------------------------------------------------------------------------------\nstatic func is_freed(obj):\n\tvar wr = weakref(obj)\n\treturn !(wr.get_ref() and str(obj) != '<Freed Object>')\n\n\n# ------------------------------------------------------------------------------\n# Pretty self explanitory.\n# ------------------------------------------------------------------------------\nstatic func is_not_freed(obj):\n\treturn !is_freed(obj)\n\n\n# ------------------------------------------------------------------------------\n# Checks if the passed in object is a GUT Double or Partial Double.\n# ------------------------------------------------------------------------------\nstatic func is_double(obj):\n\tvar to_return = false\n\tif(typeof(obj) == TYPE_OBJECT and is_instance_valid(obj)):\n\t\tto_return = obj.has_method('__gutdbl_check_method__')\n\treturn to_return\n\n\n# ------------------------------------------------------------------------------\n# Checks an object to see if it is a GDScriptNativeClass\n# ------------------------------------------------------------------------------\nstatic func is_native_class(thing):\n\tvar it_is = false\n\tif(typeof(thing) == TYPE_OBJECT):\n\t\tit_is = str(thing).begins_with(\"<GDScriptNativeClass#\")\n\treturn it_is\n\n\n# ------------------------------------------------------------------------------\n# Checks if the passed in is an instance of a class\n# ------------------------------------------------------------------------------\nstatic func is_instance(obj):\n\treturn typeof(obj) == TYPE_OBJECT and \\\n\t\t!is_native_class(obj) and \\\n\t\t!obj.has_method('new') and \\\n\t\t!obj.has_method('instantiate')\n\n\n# ------------------------------------------------------------------------------\n# Checks if the passed in is a GDScript\n# ------------------------------------------------------------------------------\nstatic func is_gdscript(obj):\n\treturn typeof(obj) == TYPE_OBJECT and str(obj).begins_with('<GDScript#')\n\n\n# ------------------------------------------------------------------------------\n# Checks if the passed in is an inner class\n#\n# Looks like the resource_path will be populated for gdscripts, and not populated\n# for gdscripts inside a gdscript.\n# ------------------------------------------------------------------------------\nstatic func is_inner_class(obj):\n\treturn is_gdscript(obj) and obj.resource_path == ''\n\n\n# ------------------------------------------------------------------------------\n# Returns an array of values by calling get(property) on each element in source\n# ------------------------------------------------------------------------------\nstatic func extract_property_from_array(source, property):\n\tvar to_return = []\n\tfor i in (source.size()):\n\t\tto_return.append(source[i].get(property))\n\treturn to_return\n\n\n# ------------------------------------------------------------------------------\n# true if what is passed in is null or an empty string.\n# ------------------------------------------------------------------------------\nstatic func is_null_or_empty(text):\n\treturn text == null or text == ''\n\n\n# ------------------------------------------------------------------------------\n# Get the name of a native class or null if the object passed in is not a\n# native class.\n# ------------------------------------------------------------------------------\nstatic func get_native_class_name(thing):\n\tvar to_return = null\n\tif(is_native_class(thing)):\n\t\tif(gdscript_native_class_names_by_type.has(thing)):\n\t\t\tto_return = gdscript_native_class_names_by_type[thing]\n\t\telse:\n\t\t\tvar newone = thing.new()\n\t\t\tto_return = newone.get_class()\n\t\t\tif(!newone is RefCounted):\n\t\t\t\tnewone.free()\n\t\t\tgdscript_native_class_names_by_type[thing] = to_return\n\treturn to_return\n\n\n# ------------------------------------------------------------------------------\n# Write a file.\n# ------------------------------------------------------------------------------\nstatic func write_file(path, content):\n\tvar f = FileAccess.open(path, FileAccess.WRITE)\n\tif(f != null):\n\t\tf.store_string(content)\n\tf = null;\n\n\treturn FileAccess.get_open_error()\n\n\n# ------------------------------------------------------------------------------\n# Returns the text of a file or an empty string if the file could not be opened.\n# ------------------------------------------------------------------------------\nstatic func get_file_as_text(path):\n\tvar to_return = ''\n\tvar f = FileAccess.open(path, FileAccess.READ)\n\tif(f != null):\n\t\tto_return = f.get_as_text()\n\telse:\n\t\tvar err = FileAccess.get_open_error()\n\t\t_lgr.error(str('Could not open file ', path, '.  Error ', err))\n\tf = null\n\treturn to_return\n\n\n# ------------------------------------------------------------------------------\n# Loops through an array of things and calls a method or checks a property on\n# each element until it finds the returned value.  -1 is returned if not found\n# or the index is returned if found.\n# ------------------------------------------------------------------------------\nstatic func search_array_idx(ar, prop_method, value):\n\tvar found = false\n\tvar idx = 0\n\n\twhile(idx < ar.size() and !found):\n\t\tvar item = ar[idx]\n\t\tvar prop = item.get(prop_method)\n\t\tif(!(prop is Callable)):\n\t\t\tif(item.get(prop_method) == value):\n\t\t\t\tfound = true\n\t\telif(prop != null):\n\t\t\tvar called_val = prop.call()\n\t\t\tif(called_val == value):\n\t\t\t\tfound = true\n\n\t\tif(!found):\n\t\t\tidx += 1\n\n\tif(found):\n\t\treturn idx\n\telse:\n\t\treturn -1\n\n\n# ------------------------------------------------------------------------------\n# Loops through an array of things and calls a method or checks a property on\n# each element until it finds the returned value.  The item in the array is\n# returned or null if it is not found (this method originally came first).\n# ------------------------------------------------------------------------------\nstatic func search_array(ar, prop_method, value):\n\tvar idx = search_array_idx(ar, prop_method, value)\n\n\tif(idx != -1):\n\t\treturn ar[idx]\n\telse:\n\t\treturn null\n\n\nstatic func are_datatypes_same(got, expected):\n\treturn !(typeof(got) != typeof(expected) and got != null and expected != null)\n\n\nstatic func get_script_text(obj):\n\treturn obj.get_script().get_source_code()\n\n\n# func get_singleton_by_name(name):\n# \tvar source = str(\"var singleton = \", name)\n# \tvar script = GDScript.new()\n# \tscript.set_source_code(source)\n# \tscript.reload()\n# \treturn script.new().singleton\n\n\nstatic func dec2bistr(decimal_value, max_bits = 31):\n\tvar binary_string = \"\"\n\tvar temp\n\tvar count = max_bits\n\n\twhile(count >= 0):\n\t\ttemp = decimal_value >> count\n\t\tif(temp & 1):\n\t\t\tbinary_string = binary_string + \"1\"\n\t\telse:\n\t\t\tbinary_string = binary_string + \"0\"\n\t\tcount -= 1\n\n\treturn binary_string\n\n\nstatic func add_line_numbers(contents):\n\tif(contents == null):\n\t\treturn ''\n\n\tvar to_return = \"\"\n\tvar lines = contents.split(\"\\n\")\n\tvar line_num = 1\n\tfor line in lines:\n\t\tvar line_str = str(line_num).lpad(6, ' ')\n\t\tto_return += str(line_str, ' |', line, \"\\n\")\n\t\tline_num += 1\n\treturn to_return\n\n\nstatic func get_display_size():\n\treturn Engine.get_main_loop().get_viewport().get_visible_rect()\n\n\n\nstatic func find_method_meta(methods, method_name):\n\tvar meta = null\n\tvar idx = 0\n\twhile (idx < methods.size() and meta == null):\n\t\tvar m = methods[idx]\n\t\tif(m.name == method_name):\n\t\t\tmeta = m\n\t\tidx += 1\n\n\treturn meta\n\n\nstatic func get_method_meta(object, method_name):\n\treturn find_method_meta(object.get_method_list(), method_name)\n\n# ##############################################################################\n#(G)odot (U)nit (T)est class\n#\n# ##############################################################################\n# The MIT License (MIT)\n# =====================\n#\n# Copyright (c) 2025 Tom \"Butch\" Wesley\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 THE\n# 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# ##############################################################################\n"
  },
  {
    "path": "demo/addons/gut/utils.gd.uid",
    "content": "uid://dbfbnvoq5osf2\n"
  },
  {
    "path": "demo/addons/gut/version_conversion.gd",
    "content": "class ConfigurationUpdater:\n\tvar EditorGlobals = load(\"res://addons/gut/gui/editor_globals.gd\")\n\n\tfunc warn(message):\n\t\tprint('GUT Warning:  ', message)\n\n\n\tfunc info(message):\n\t\tprint(\"GUT Info:  \", message)\n\n\n\tfunc moved_file(from, to):\n\t\tif(FileAccess.file_exists(from) and !FileAccess.file_exists(to)):\n\t\t\tinfo(str('Copying [', from, '] to [', to, ']'))\n\t\t\tvar result = DirAccess.copy_absolute(from, to)\n\t\t\tif(result != OK):\n\t\t\t\twarn(str('Could not copy [', from, '] to [', to, ']'))\n\n\t\tif(FileAccess.file_exists(from) and FileAccess.file_exists(to)):\n\t\t\twarn(str('File [', from, '] has been moved to [', to, \"].\\n    You can delete \", from))\n\n\n\tfunc move_user_file(from, to):\n\t\tif(from.begins_with('user://') and to.begins_with('user://')):\n\t\t\tif(FileAccess.file_exists(from) and !FileAccess.file_exists(to)):\n\t\t\t\tinfo(str('Moving [', from, '] to [', to, ']'))\n\t\t\t\tvar result = DirAccess.copy_absolute(from, to)\n\t\t\t\tif(result == OK):\n\t\t\t\t\tinfo(str('    ', 'Created ', to))\n\t\t\t\t\tresult = DirAccess.remove_absolute(from)\n\t\t\t\t\tif(result != OK):\n\t\t\t\t\t\twarn(str('    ', 'Could not delete ', from))\n\t\t\t\t\telse:\n\t\t\t\t\t\tinfo(str('    ', 'Deleted ', from))\n\t\t\t\telse:\n\t\t\t\t\twarn(str('    ', 'Could not copy [', from, '] to [', to, ']'))\n\t\telse:\n\t\t\twarn(str('Attempt to move_user_file with files not in user:// ', from, '->', to))\n\n\n\tfunc remove_user_file(which):\n\t\tif(which.begins_with('user://') and FileAccess.file_exists(which)):\n\t\t\tinfo(str('Deleting obsolete file ', which))\n\t\t\tvar result = DirAccess.remove_absolute(which)\n\t\t\tif(result != OK):\n\t\t\t\twarn(str('    ', 'Could not delete ', which))\n\t\t\telse:\n\t\t\t\tinfo(str('    ', 'Deleted ', which))\n\nclass v9_2_0:\n\textends ConfigurationUpdater\n\n\tfunc validate():\n\t\tmoved_file('res://.gut_editor_config.json', EditorGlobals.editor_run_gut_config_path)\n\t\tmoved_file('res://.gut_editor_shortcuts.cfg', EditorGlobals.editor_shortcuts_path)\n\t\tremove_user_file('user://.gut_editor.bbcode')\n\t\tremove_user_file('user://.gut_editor.json')\n\n# list=Array[Dictionary]([{\n# \"base\": &\"RefCounted\",\n# \"class\": &\"DynamicGutTest\",\n# \"icon\": \"\",\n# \"language\": &\"GDScript\",\n# \"path\": \"res://test/resources/tools/dynamic_gut_test.gd\"\n# }, {\n# \"base\": &\"RefCounted\",\n# \"class\": &\"GutDoubleTestInnerClasses\",\n# \"icon\": \"\",\n# \"language\": &\"GDScript\",\n# \"path\": \"res://test/resources/doubler_test_objects/inner_classes.gd\"\n# }, ... ])\nstatic func get_missing_gut_class_names() -> Array:\n\tvar gut_class_names = [\n\t\t\"GutErrorTracker\",\n\t\t\"GutHookScript\",\n\t\t\"GutInputFactory\",\n\t\t\"GutInputSender\",\n\t\t\"GutMain\",\n\t\t\"GutStringUtils\",\n\t\t\"GutTest\",\n\t\t\"GutTrackedError\",\n\t\t\"GutUtils\",\n\t]\n\n\tvar class_cach_path = 'res://.godot/global_script_class_cache.cfg'\n\tvar cfg = ConfigFile.new()\n\tcfg.load(class_cach_path)\n\n\tvar all_class_names = {}\n\tvar missing  = []\n\tvar class_cache_entries = cfg.get_value('', 'list', [])\n\n\tfor entry in class_cache_entries:\n\t\tif(entry.path.begins_with(&\"res://addons/gut/\")):\n\t\t\t# print(entry[\"class\"], ':  ', entry[\"path\"])\n\t\t\tall_class_names[entry[\"class\"]] = entry\n\n\tfor cn in gut_class_names:\n\t\tif(!all_class_names.has(cn)):\n\t\t\tmissing.append(cn)\n\n\treturn missing\n\n\nstatic func error_if_not_all_classes_imported() -> bool:\n\tvar missing_class_names = get_missing_gut_class_names()\n\tif(missing_class_names.size() > 0):\n\t\tpush_error(str(\"Some GUT class_names have not been imported.  Please restart the Editor or run godot --headless --import\\n\",\n\t\t\t\"Missing class_names:  \",\n\t\t\tmissing_class_names))\n\t\treturn true\n\telse:\n\t\treturn false\n\n\n\n\nstatic func convert():\n\tvar inst = v9_2_0.new()\n\tinst.validate()\n"
  },
  {
    "path": "demo/addons/gut/version_conversion.gd.uid",
    "content": "uid://c8twdri50qrkb\n"
  },
  {
    "path": "demo/addons/gut/version_numbers.gd",
    "content": "# ##############################################################################\n#\n# ##############################################################################\nclass VerNumTools:\n\n\tstatic func _make_version_array_from_string(v):\n\t\tvar parts = Array(v.split('.'))\n\t\tfor i in range(parts.size()):\n\t\t\tvar int_val = parts[i].to_int()\n\t\t\tif(str(int_val) == parts[i]):\n\t\t\t\tparts[i] = parts[i].to_int()\n\t\treturn parts\n\n\n\tstatic func make_version_array(v):\n\t\tvar to_return = []\n\t\tif(typeof(v) == TYPE_STRING):\n\t\t\tto_return = _make_version_array_from_string(v)\n\t\telif(typeof(v) == TYPE_DICTIONARY):\n\t\t\treturn [v.major, v.minor, v.patch]\n\t\telif(typeof(v) == TYPE_ARRAY):\n\t\t\tto_return = v\n\t\treturn to_return\n\n\n\tstatic func make_version_string(version_parts):\n\t\tvar to_return = 'x.x.x'\n\t\tif(typeof(version_parts) == TYPE_ARRAY):\n\t\t\tto_return =  \".\".join(version_parts)\n\t\telif(typeof(version_parts) == TYPE_DICTIONARY):\n\t\t\tto_return = str(version_parts.major,  '.',  version_parts.minor,  '.',  version_parts.patch)\n\t\telif(typeof(version_parts) == TYPE_STRING):\n\t\t\tto_return = version_parts\n\t\treturn to_return\n\n\n\tstatic func is_version_gte(version, required):\n\t\tvar is_ok = null\n\t\tvar v = make_version_array(version)\n\t\tvar r = make_version_array(required)\n\n\t\tvar idx = 0\n\t\twhile(is_ok == null and idx < v.size() and idx < r.size()):\n\t\t\tif(v[idx] > r[idx]):\n\t\t\t\tis_ok = true\n\t\t\telif(v[idx] < r[idx]):\n\t\t\t\tis_ok = false\n\n\t\t\tidx += 1\n\n\t\t# still null means each index was the same.\n\t\treturn GutUtils.nvl(is_ok, true)\n\n\n\tstatic func is_version_eq(version, expected):\n\t\tvar version_array = make_version_array(version)\n\t\tvar expected_array = make_version_array(expected)\n\n\t\tif(expected_array.size() > version_array.size()):\n\t\t\treturn false\n\n\t\tvar is_version = true\n\t\tvar i = 0\n\t\twhile(i < expected_array.size() and i < version_array.size() and is_version):\n\t\t\tif(expected_array[i] == version_array[i]):\n\t\t\t\ti += 1\n\t\t\telse:\n\t\t\t\tis_version = false\n\n\t\treturn is_version\n\n\n\tstatic func is_godot_version_eq(expected):\n\t\treturn VerNumTools.is_version_eq(Engine.get_version_info(), expected)\n\n\n\tstatic func is_godot_version_gte(expected):\n\t\treturn VerNumTools.is_version_gte(Engine.get_version_info(), expected)\n\n\n\n\n# ##############################################################################\n#\n# ##############################################################################\nvar gut_version = '0.0.0'\nvar required_godot_version = '0.0.0'\n\nfunc _init(gut_v = gut_version, required_godot_v = required_godot_version):\n\tgut_version = gut_v\n\trequired_godot_version = required_godot_v\n\n\n# ------------------------------------------------------------------------------\n# Blurb of text with GUT and Godot versions.\n# ------------------------------------------------------------------------------\nfunc get_version_text():\n\tvar v_info = Engine.get_version_info()\n\tvar gut_version_info =  str('GUT version:  ', gut_version)\n\tvar godot_version_info  = str('Godot version:  ', v_info.major,  '.',  v_info.minor,  '.',  v_info.patch)\n\treturn godot_version_info + \"\\n\" + gut_version_info\n\n\n# ------------------------------------------------------------------------------\n# Returns a nice string for erroring out when we have a bad Godot version.\n# ------------------------------------------------------------------------------\nfunc get_bad_version_text():\n\tvar info = Engine.get_version_info()\n\tvar gd_version = str(info.major, '.', info.minor, '.', info.patch)\n\treturn 'GUT ' + gut_version + ' requires Godot ' + required_godot_version + \\\n\t\t' or greater.  Godot version is ' + gd_version\n\n\n# ------------------------------------------------------------------------------\n# Checks the Godot version against required_godot_version.\n# ------------------------------------------------------------------------------\nfunc is_godot_version_valid():\n\treturn VerNumTools.is_version_gte(Engine.get_version_info(), required_godot_version)\n\n\nfunc make_godot_version_string():\n\treturn VerNumTools.make_version_string(Engine.get_version_info())\n"
  },
  {
    "path": "demo/addons/gut/version_numbers.gd.uid",
    "content": "uid://b4bb6lchs5uba\n"
  },
  {
    "path": "demo/addons/gut/warnings_manager.gd",
    "content": "const IGNORE = 0\nconst WARN = 1\nconst ERROR = 2\n\n\nconst WARNING_LOOKUP = {\n\tIGNORE : 'IGNORE',\n\tWARN : 'WARN',\n\tERROR : 'ERROR'\n}\n\nconst GDSCRIPT_WARNING = 'debug/gdscript/warnings/'\n\n\n# ---------------------------------------\n# Static\n# ---------------------------------------\nstatic var _static_init_called = false\n# This is static and set in _static_init so that we can get the current settings as\n# soon as possible.\nstatic var _project_warnings : Dictionary = {}\n\nstatic var _disabled = false\n# should never be true, unless it is, but it shouldn't be.  Whatever it is, it\n# should stay the same for the entire run.  Read only.\nstatic var disabled = _disabled:\n\tget: return _disabled\n\tset(val):pass\n\nstatic var project_warnings := {} :\n\tget:\n\t\t# somehow this gets called before _project_warnings is initialized when\n\t\t# loading a project in the editor.  It causes an error stating that\n\t\t# duplicate can't be called on nil.  It seems there might be an\n\t\t# implicit \"get\" call happening.  Using push_error I saw a message\n\t\t# in this method, but not one from _static_init upon loading the project\n\t\tif(_static_init_called):\n\t\t\treturn _project_warnings.duplicate()\n\t\telse:\n\t\t\treturn {}\n\tset(val): pass\n\n\nstatic func _static_init():\n\t_project_warnings = create_warnings_dictionary_from_project_settings()\n\t_static_init_called = true\n\tif(disabled):\n\t\tprint(\"\"\"\n\t\t!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\t\t!! Warnings Manager has been disabled\n\t\t!!\n\t\t!! Do not push this up buddy\n\t\t!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\t\t\"\"\".dedent())\n\n\nstatic func are_warnings_enabled():\n\treturn ProjectSettings.get(str(GDSCRIPT_WARNING, 'enable'))\n\n\n## Turn all warnings on/off.  Use reset_warnings to restore the original value.\nstatic func enable_warnings(should=true):\n\tif(disabled):\n\t\treturn\n\tProjectSettings.set(str(GDSCRIPT_WARNING, 'enable'), should)\n\n\n## Turn on/off excluding addons.  Use reset_warnings to restore the original value.\nstatic func exclude_addons(should=true):\n\tif(disabled):\n\t\treturn\n\tProjectSettings.set(str(GDSCRIPT_WARNING, 'exclude_addons'), should)\n\n\n## Resets warning settings to what they are set to in Project Settings\nstatic func reset_warnings():\n\tapply_warnings_dictionary(_project_warnings)\n\n\n\nstatic func set_project_setting_warning(warning_name : String, value : Variant):\n\tif(disabled):\n\t\treturn\n\n\tvar property_name = str(GDSCRIPT_WARNING, warning_name)\n\t# This check will generate a warning if the setting does not exist\n\tif(property_name in ProjectSettings):\n\t\tProjectSettings.set(property_name, value)\n\n\nstatic func apply_warnings_dictionary(warning_values : Dictionary):\n\tif(disabled):\n\t\treturn\n\n\tfor key in warning_values:\n\t\tset_project_setting_warning(key, warning_values[key])\n\n\nstatic func create_ignore_all_dictionary():\n\treturn replace_warnings_values(project_warnings, -1, IGNORE)\n\n\nstatic func create_warn_all_warnings_dictionary():\n\treturn replace_warnings_values(project_warnings, -1, WARN)\n\n\nstatic func replace_warnings_with_ignore(dict):\n\treturn replace_warnings_values(dict, WARN, IGNORE)\n\n\nstatic func replace_errors_with_warnings(dict):\n\treturn replace_warnings_values(dict, ERROR, WARN)\n\n\nstatic func replace_warnings_values(dict, replace_this, with_this):\n\tvar to_return = dict.duplicate()\n\tfor key in to_return:\n\t\tif(typeof(to_return[key]) == TYPE_INT and (replace_this == -1 or to_return[key] == replace_this)):\n\t\t\tto_return[key] = with_this\n\treturn to_return\n\n\nstatic func create_warnings_dictionary_from_project_settings() -> Dictionary :\n\tvar props = ProjectSettings.get_property_list()\n\tvar to_return = {}\n\tfor i in props.size():\n\t\tif(props[i].name.begins_with(GDSCRIPT_WARNING)):\n\t\t\tvar prop_name = props[i].name.replace(GDSCRIPT_WARNING, '')\n\t\t\tto_return[prop_name] = ProjectSettings.get(props[i].name)\n\treturn to_return\n\n\nstatic func print_warnings_dictionary(which : Dictionary):\n\tvar is_valid = true\n\tfor key in which:\n\t\tvar value_str = str(which[key])\n\t\tif(_project_warnings.has(key)):\n\t\t\tif(typeof(which[key]) == TYPE_INT):\n\t\t\t\tif(WARNING_LOOKUP.has(which[key])):\n\t\t\t\t\tvalue_str = WARNING_LOOKUP[which[key]]\n\t\t\t\telse:\n\t\t\t\t\tpush_warning(str(which[key], ' is not a valid value for ', key))\n\t\t\t\t\tis_valid = false\n\t\telse:\n\t\t\tpush_warning(str(key, ' is not a valid warning setting'))\n\t\t\tis_valid = false\n\t\tvar s = str(key, ' = ', value_str)\n\t\tprint(s)\n\treturn is_valid\n\n\nstatic func load_script_ignoring_all_warnings(path : String) -> Variant:\n\treturn load_script_using_custom_warnings(path, create_ignore_all_dictionary())\n\n\nstatic func load_script_using_custom_warnings(path : String, warnings_dictionary : Dictionary) -> Variant:\n\tvar current_warns = create_warnings_dictionary_from_project_settings()\n\tapply_warnings_dictionary(warnings_dictionary)\n\tvar s = load(path)\n\tapply_warnings_dictionary(current_warns)\n\n\treturn s\n"
  },
  {
    "path": "demo/addons/gut/warnings_manager.gd.uid",
    "content": "uid://blo71surxlb13\n"
  },
  {
    "path": "demo/appstore.png.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://c47slho6dd1u2\"\npath=\"res://.godot/imported/appstore.png-0b2f85bf34c98898c164059732fb30dc.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://appstore.png\"\ndest_files=[\"res://.godot/imported/appstore.png-0b2f85bf34c98898c164059732fb30dc.ctex\"]\n\n[params]\n\ncompress/mode=0\ncompress/high_quality=false\ncompress/lossy_quality=0.7\ncompress/uastc_level=0\ncompress/rdo_quality_loss=0.0\ncompress/hdr_compression=1\ncompress/normal_map=0\ncompress/channel_pack=0\nmipmaps/generate=false\nmipmaps/limit=-1\nroughness/mode=0\nroughness/src_normal=\"\"\nprocess/channel_remap/red=0\nprocess/channel_remap/green=1\nprocess/channel_remap/blue=2\nprocess/channel_remap/alpha=3\nprocess/fix_alpha_border=true\nprocess/premult_alpha=false\nprocess/normal_map_invert_y=false\nprocess/hdr_as_srgb=false\nprocess/hdr_clamp_exposure=false\nprocess/size_limit=0\ndetect_3d/compress_to=1\n"
  },
  {
    "path": "demo/assets/Music/License.txt",
    "content": "\t\n\n\tMusic Jingles\n\n\tby  Kenney Vleugels (Kenney.nl)\n\n\t\t\t------------------------------\n\n\tLicense (Creative Commons Zero, CC0)\n\thttp://creativecommons.org/publicdomain/zero/1.0/\n\n\tYou may use these assets in personal and commercial projects.\n\tCredit (Kenney or www.kenney.nl) would be nice but is not mandatory.\n\n\t\t\t------------------------------\n\n\tDonate:   http://support.kenney.nl\n\tRequest:  http://request.kenney.nl\n\n\tFollow on Twitter for updates:\n\t@KenneyNL"
  },
  {
    "path": "demo/assets/Music/jingles_SAX07.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://bbg1iwsy4av7p\"\npath=\"res://.godot/imported/jingles_SAX07.ogg-e0a2e38d4fb098d3cc5be1127eac0de1.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Music/jingles_SAX07.ogg\"\ndest_files=[\"res://.godot/imported/jingles_SAX07.ogg-e0a2e38d4fb098d3cc5be1127eac0de1.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/beltHandle1.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://dydllsb4naikb\"\npath=\"res://.godot/imported/beltHandle1.ogg-5b80474bde850e2e07979e0b9dd05a40.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/beltHandle1.ogg\"\ndest_files=[\"res://.godot/imported/beltHandle1.ogg-5b80474bde850e2e07979e0b9dd05a40.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/beltHandle2.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://ctatqi5eacvti\"\npath=\"res://.godot/imported/beltHandle2.ogg-b75cd2508ee0ee251254411db27d1f2e.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/beltHandle2.ogg\"\ndest_files=[\"res://.godot/imported/beltHandle2.ogg-b75cd2508ee0ee251254411db27d1f2e.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/bookClose.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://d2v8bwyvepp4g\"\npath=\"res://.godot/imported/bookClose.ogg-fa4b91a05f6232aedfc7b0b0f769ace8.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/bookClose.ogg\"\ndest_files=[\"res://.godot/imported/bookClose.ogg-fa4b91a05f6232aedfc7b0b0f769ace8.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/bookFlip1.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://cyye74esp88lu\"\npath=\"res://.godot/imported/bookFlip1.ogg-c00a802f57276e4c154c6490a1adfdb1.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/bookFlip1.ogg\"\ndest_files=[\"res://.godot/imported/bookFlip1.ogg-c00a802f57276e4c154c6490a1adfdb1.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/bookFlip2.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://biitt5nu0fx5o\"\npath=\"res://.godot/imported/bookFlip2.ogg-844d4ca357872e8d6f285274de218590.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/bookFlip2.ogg\"\ndest_files=[\"res://.godot/imported/bookFlip2.ogg-844d4ca357872e8d6f285274de218590.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/bookFlip3.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://cnulfritsm0sb\"\npath=\"res://.godot/imported/bookFlip3.ogg-dc8e2bfa9dc330e8376e1b2fbbe16170.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/bookFlip3.ogg\"\ndest_files=[\"res://.godot/imported/bookFlip3.ogg-dc8e2bfa9dc330e8376e1b2fbbe16170.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/bookOpen.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://dbqp1pg2g5pmk\"\npath=\"res://.godot/imported/bookOpen.ogg-f5eca4dcf9579626efc8d4cd81bac2c8.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/bookOpen.ogg\"\ndest_files=[\"res://.godot/imported/bookOpen.ogg-f5eca4dcf9579626efc8d4cd81bac2c8.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/bookPlace1.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://cgq1hn6e7xc3w\"\npath=\"res://.godot/imported/bookPlace1.ogg-9473a111ac56c964e35a3ce405643e88.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/bookPlace1.ogg\"\ndest_files=[\"res://.godot/imported/bookPlace1.ogg-9473a111ac56c964e35a3ce405643e88.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/bookPlace2.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://c4c36a70ro865\"\npath=\"res://.godot/imported/bookPlace2.ogg-390a4879e48e2dcfd973c3e012f3b9b8.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/bookPlace2.ogg\"\ndest_files=[\"res://.godot/imported/bookPlace2.ogg-390a4879e48e2dcfd973c3e012f3b9b8.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/bookPlace3.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://bhyb2rc3qk5ls\"\npath=\"res://.godot/imported/bookPlace3.ogg-83530db2570a1d448e09955cb28b7c52.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/bookPlace3.ogg\"\ndest_files=[\"res://.godot/imported/bookPlace3.ogg-83530db2570a1d448e09955cb28b7c52.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/chop.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://xp6pojfjt6qq\"\npath=\"res://.godot/imported/chop.ogg-c6363f08403778fa8c2db7513103fe64.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/chop.ogg\"\ndest_files=[\"res://.godot/imported/chop.ogg-c6363f08403778fa8c2db7513103fe64.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/cloth1.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://drikcdbtu6kga\"\npath=\"res://.godot/imported/cloth1.ogg-454aba1a0a43c6224cc67f1fa4c3d27a.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/cloth1.ogg\"\ndest_files=[\"res://.godot/imported/cloth1.ogg-454aba1a0a43c6224cc67f1fa4c3d27a.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/cloth2.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://dthttp8pdmayq\"\npath=\"res://.godot/imported/cloth2.ogg-bae99958d207b4e17f9cd7a91b36afdf.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/cloth2.ogg\"\ndest_files=[\"res://.godot/imported/cloth2.ogg-bae99958d207b4e17f9cd7a91b36afdf.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/cloth3.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://du5tovx03lxt6\"\npath=\"res://.godot/imported/cloth3.ogg-a4237326ad50d528b3965cb7b78c88c6.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/cloth3.ogg\"\ndest_files=[\"res://.godot/imported/cloth3.ogg-a4237326ad50d528b3965cb7b78c88c6.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/cloth4.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://bfq5cd33xqsiq\"\npath=\"res://.godot/imported/cloth4.ogg-ce7ca0df36c75e48c93a562266b170ee.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/cloth4.ogg\"\ndest_files=[\"res://.godot/imported/cloth4.ogg-ce7ca0df36c75e48c93a562266b170ee.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/clothBelt.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://b0jvof842h4df\"\npath=\"res://.godot/imported/clothBelt.ogg-8ad677919ef512f9ae399d549c502b58.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/clothBelt.ogg\"\ndest_files=[\"res://.godot/imported/clothBelt.ogg-8ad677919ef512f9ae399d549c502b58.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/clothBelt2.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://8uwk7rtghtbb\"\npath=\"res://.godot/imported/clothBelt2.ogg-f63060ada3e32df8afab079abd87beb5.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/clothBelt2.ogg\"\ndest_files=[\"res://.godot/imported/clothBelt2.ogg-f63060ada3e32df8afab079abd87beb5.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/creak1.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://bojslx0dun6se\"\npath=\"res://.godot/imported/creak1.ogg-db9b54f10b9fa283cdc1e795aa365995.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/creak1.ogg\"\ndest_files=[\"res://.godot/imported/creak1.ogg-db9b54f10b9fa283cdc1e795aa365995.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/creak2.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://bd61n3r2mj32l\"\npath=\"res://.godot/imported/creak2.ogg-a930fc882a2b6e71a0e476f77428f4c1.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/creak2.ogg\"\ndest_files=[\"res://.godot/imported/creak2.ogg-a930fc882a2b6e71a0e476f77428f4c1.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/creak3.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://dfdu8w2hbcild\"\npath=\"res://.godot/imported/creak3.ogg-d1dd170afbc09d20efb8b28b7994904d.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/creak3.ogg\"\ndest_files=[\"res://.godot/imported/creak3.ogg-d1dd170afbc09d20efb8b28b7994904d.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/doorClose_1.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://uwhyij31kf6x\"\npath=\"res://.godot/imported/doorClose_1.ogg-fb2bd4d8c58de96b58c5a92f8db46991.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/doorClose_1.ogg\"\ndest_files=[\"res://.godot/imported/doorClose_1.ogg-fb2bd4d8c58de96b58c5a92f8db46991.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/doorClose_2.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://bqoig28844rx0\"\npath=\"res://.godot/imported/doorClose_2.ogg-fb315789363270c69acf8b9a7b168a86.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/doorClose_2.ogg\"\ndest_files=[\"res://.godot/imported/doorClose_2.ogg-fb315789363270c69acf8b9a7b168a86.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/doorClose_3.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://bwuvku8j6rv23\"\npath=\"res://.godot/imported/doorClose_3.ogg-56114bb9e0a5e61869880908f3b5a8a5.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/doorClose_3.ogg\"\ndest_files=[\"res://.godot/imported/doorClose_3.ogg-56114bb9e0a5e61869880908f3b5a8a5.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/doorClose_4.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://dbkktxrm5c4y8\"\npath=\"res://.godot/imported/doorClose_4.ogg-1bb649f6ab99be6253ac5224baf07103.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/doorClose_4.ogg\"\ndest_files=[\"res://.godot/imported/doorClose_4.ogg-1bb649f6ab99be6253ac5224baf07103.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/doorOpen_1.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://cpi37teqbwopp\"\npath=\"res://.godot/imported/doorOpen_1.ogg-2cc2988618fb7d031150927eb359efc9.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/doorOpen_1.ogg\"\ndest_files=[\"res://.godot/imported/doorOpen_1.ogg-2cc2988618fb7d031150927eb359efc9.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/doorOpen_2.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://dsmaot3lqm5ms\"\npath=\"res://.godot/imported/doorOpen_2.ogg-4916d9512b4ac3e4ba88acf90a1f5af9.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/doorOpen_2.ogg\"\ndest_files=[\"res://.godot/imported/doorOpen_2.ogg-4916d9512b4ac3e4ba88acf90a1f5af9.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/drawKnife1.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://dw2txvwv5bub6\"\npath=\"res://.godot/imported/drawKnife1.ogg-cd7700ce71ea897983a04459f74736fd.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/drawKnife1.ogg\"\ndest_files=[\"res://.godot/imported/drawKnife1.ogg-cd7700ce71ea897983a04459f74736fd.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/drawKnife2.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://dcssqqmaqgwql\"\npath=\"res://.godot/imported/drawKnife2.ogg-3277966fa0ccf19387948aca56c2f148.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/drawKnife2.ogg\"\ndest_files=[\"res://.godot/imported/drawKnife2.ogg-3277966fa0ccf19387948aca56c2f148.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/drawKnife3.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://dvxp23851ib52\"\npath=\"res://.godot/imported/drawKnife3.ogg-7ff84ee2dc7dd20ed24d01544c56cdc6.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/drawKnife3.ogg\"\ndest_files=[\"res://.godot/imported/drawKnife3.ogg-7ff84ee2dc7dd20ed24d01544c56cdc6.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/dropLeather.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://dfou14surgda2\"\npath=\"res://.godot/imported/dropLeather.ogg-f379e54b79e4375d25e07e40c5f14d56.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/dropLeather.ogg\"\ndest_files=[\"res://.godot/imported/dropLeather.ogg-f379e54b79e4375d25e07e40c5f14d56.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/footstep00.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://b0y64o7e0p22i\"\npath=\"res://.godot/imported/footstep00.ogg-c8328f3a28a4cf747f25018e77b22788.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/footstep00.ogg\"\ndest_files=[\"res://.godot/imported/footstep00.ogg-c8328f3a28a4cf747f25018e77b22788.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/footstep01.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://cpyem21efo8gc\"\npath=\"res://.godot/imported/footstep01.ogg-bd79989daf496e93c5d8254c3f5e99a1.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/footstep01.ogg\"\ndest_files=[\"res://.godot/imported/footstep01.ogg-bd79989daf496e93c5d8254c3f5e99a1.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/footstep02.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://1nomeut88u4q\"\npath=\"res://.godot/imported/footstep02.ogg-304bdc4ea220c3fdd746a1fe8a27790f.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/footstep02.ogg\"\ndest_files=[\"res://.godot/imported/footstep02.ogg-304bdc4ea220c3fdd746a1fe8a27790f.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/footstep03.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://becp8wddafyp4\"\npath=\"res://.godot/imported/footstep03.ogg-16361b106b1414b04013abe70392756d.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/footstep03.ogg\"\ndest_files=[\"res://.godot/imported/footstep03.ogg-16361b106b1414b04013abe70392756d.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/footstep04.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://cn1ca7ilgbxg5\"\npath=\"res://.godot/imported/footstep04.ogg-fa0eeefab9b4265ea9c83433c7eff9e6.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/footstep04.ogg\"\ndest_files=[\"res://.godot/imported/footstep04.ogg-fa0eeefab9b4265ea9c83433c7eff9e6.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/footstep05.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://dfpimayyis68b\"\npath=\"res://.godot/imported/footstep05.ogg-6fd3eb14d963635ff2f473f45c6f2314.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/footstep05.ogg\"\ndest_files=[\"res://.godot/imported/footstep05.ogg-6fd3eb14d963635ff2f473f45c6f2314.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/footstep06.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://odkpye6s6omw\"\npath=\"res://.godot/imported/footstep06.ogg-d73a658a91f3fb7f652648268887f089.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/footstep06.ogg\"\ndest_files=[\"res://.godot/imported/footstep06.ogg-d73a658a91f3fb7f652648268887f089.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/footstep07.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://baq0coor6bscw\"\npath=\"res://.godot/imported/footstep07.ogg-26ceb5e06f5c062627ce1a0156ac6645.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/footstep07.ogg\"\ndest_files=[\"res://.godot/imported/footstep07.ogg-26ceb5e06f5c062627ce1a0156ac6645.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/footstep08.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://b2ox2fswnhq55\"\npath=\"res://.godot/imported/footstep08.ogg-ee19c27dab4fdf314dd8dd907273e7f6.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/footstep08.ogg\"\ndest_files=[\"res://.godot/imported/footstep08.ogg-ee19c27dab4fdf314dd8dd907273e7f6.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/footstep09.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://il238x71e0be\"\npath=\"res://.godot/imported/footstep09.ogg-090ea3d9a9cc50cd7dc37e43721f4314.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/footstep09.ogg\"\ndest_files=[\"res://.godot/imported/footstep09.ogg-090ea3d9a9cc50cd7dc37e43721f4314.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/handleCoins.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://cnca8pdcea734\"\npath=\"res://.godot/imported/handleCoins.ogg-899543da392ce60fc37dd93aec297bb9.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/handleCoins.ogg\"\ndest_files=[\"res://.godot/imported/handleCoins.ogg-899543da392ce60fc37dd93aec297bb9.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/handleCoins2.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://bosfwq1n8hi6t\"\npath=\"res://.godot/imported/handleCoins2.ogg-3014b56123531c3d044358fd5904b82c.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/handleCoins2.ogg\"\ndest_files=[\"res://.godot/imported/handleCoins2.ogg-3014b56123531c3d044358fd5904b82c.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/handleSmallLeather.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://dxbl6dnn55gcy\"\npath=\"res://.godot/imported/handleSmallLeather.ogg-43b67dc9c38260d94781822f1244d69f.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/handleSmallLeather.ogg\"\ndest_files=[\"res://.godot/imported/handleSmallLeather.ogg-43b67dc9c38260d94781822f1244d69f.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/handleSmallLeather2.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://cu6tlq87dqej\"\npath=\"res://.godot/imported/handleSmallLeather2.ogg-fbb7df5a80d10bfc58e6acbea84f4ab5.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/handleSmallLeather2.ogg\"\ndest_files=[\"res://.godot/imported/handleSmallLeather2.ogg-fbb7df5a80d10bfc58e6acbea84f4ab5.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/knifeSlice.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://cxoy0f8kislgh\"\npath=\"res://.godot/imported/knifeSlice.ogg-ff3fc6d1fe55494d8e63ac86ff57a090.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/knifeSlice.ogg\"\ndest_files=[\"res://.godot/imported/knifeSlice.ogg-ff3fc6d1fe55494d8e63ac86ff57a090.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/knifeSlice2.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://bunyclrcy6qd4\"\npath=\"res://.godot/imported/knifeSlice2.ogg-b0fec775bac0402f4d8d441fc72e7222.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/knifeSlice2.ogg\"\ndest_files=[\"res://.godot/imported/knifeSlice2.ogg-b0fec775bac0402f4d8d441fc72e7222.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/licence.txt",
    "content": "Sound file from Kenney Game assets\nhttps://www.kenney.nl/\n\nLicense: (CC0 1.0 Universal) You're free to use these game assets in any project, personal or commercial. There's no need to ask permission before using these. Giving attribution is not required, but is greatly appreciated!"
  },
  {
    "path": "demo/assets/Sounds/metalClick.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://clb0yb3eitaf0\"\npath=\"res://.godot/imported/metalClick.ogg-04388905a2f91e693ddc2a0f2dc0b57a.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/metalClick.ogg\"\ndest_files=[\"res://.godot/imported/metalClick.ogg-04388905a2f91e693ddc2a0f2dc0b57a.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/metalLatch.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://dmuixaikstwpw\"\npath=\"res://.godot/imported/metalLatch.ogg-eeafac9c0415bd988baefc04749b2e94.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/metalLatch.ogg\"\ndest_files=[\"res://.godot/imported/metalLatch.ogg-eeafac9c0415bd988baefc04749b2e94.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/metalPot1.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://c2ye8r4j133h3\"\npath=\"res://.godot/imported/metalPot1.ogg-1b5f6d48fae741734b835d833696a611.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/metalPot1.ogg\"\ndest_files=[\"res://.godot/imported/metalPot1.ogg-1b5f6d48fae741734b835d833696a611.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/metalPot2.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://d38uhbft8cy1f\"\npath=\"res://.godot/imported/metalPot2.ogg-5a8d786b01c4367a67ce1185cbcba3c9.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/metalPot2.ogg\"\ndest_files=[\"res://.godot/imported/metalPot2.ogg-5a8d786b01c4367a67ce1185cbcba3c9.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/assets/Sounds/metalPot3.ogg.import",
    "content": "[remap]\n\nimporter=\"oggvorbisstr\"\ntype=\"AudioStreamOggVorbis\"\nuid=\"uid://lnvtp5i7yk4f\"\npath=\"res://.godot/imported/metalPot3.ogg-77b1a58051f91d0ebee634bce4a5e0e3.oggvorbisstr\"\n\n[deps]\n\nsource_file=\"res://assets/Sounds/metalPot3.ogg\"\ndest_files=[\"res://.godot/imported/metalPot3.ogg-77b1a58051f91d0ebee634bce4a5e0e3.oggvorbisstr\"]\n\n[params]\n\nloop=false\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "demo/default_env.tres",
    "content": "[gd_resource type=\"Environment\" load_steps=2 format=2]\n\n[sub_resource type=\"Sky\" id=1]\n\n[resource]\nbackground_mode = 2\nbackground_sky = SubResource( 1 )\n"
  },
  {
    "path": "demo/export_presets.cfg",
    "content": "[preset.0]\n\nname=\"Android\"\nplatform=\"Android\"\nrunnable=true\ndedicated_server=false\ncustom_features=\"\"\nexport_filter=\"all_resources\"\ninclude_filter=\"\"\nexclude_filter=\"\"\nexport_path=\"../../../../FMOD Gdnative Integration Demo.apk\"\nencryption_include_filters=\"\"\nencryption_exclude_filters=\"\"\nencrypt_pck=false\nencrypt_directory=false\n\n[preset.0.options]\n\ncustom_template/debug=\"\"\ncustom_template/release=\"\"\ngradle_build/use_gradle_build=true\ngradle_build/export_format=0\ngradle_build/min_sdk=\"\"\ngradle_build/target_sdk=\"\"\nplugins/FmodPlugin=true\narchitectures/armeabi-v7a=false\narchitectures/arm64-v8a=true\narchitectures/x86=false\narchitectures/x86_64=false\nversion/code=1\nversion/name=\"1.0\"\npackage/unique_name=\"com.utopiarise\"\npackage/name=\"fmodgdnative\"\npackage/signed=true\npackage/app_category=2\npackage/retain_data_on_uninstall=false\npackage/exclude_from_recents=false\nlauncher_icons/main_192x192=\"\"\nlauncher_icons/adaptive_foreground_432x432=\"\"\nlauncher_icons/adaptive_background_432x432=\"\"\ngraphics/opengl_debug=false\nxr_features/xr_mode=0\nxr_features/hand_tracking=0\nxr_features/hand_tracking_frequency=0\nxr_features/passthrough=0\nscreen/immersive_mode=true\nscreen/support_small=true\nscreen/support_normal=true\nscreen/support_large=true\nscreen/support_xlarge=true\nuser_data_backup/allow=false\ncommand_line/extra_args=\"\"\napk_expansion/enable=false\napk_expansion/SALT=\"\"\napk_expansion/public_key=\"\"\npermissions/custom_permissions=PackedStringArray()\npermissions/access_checkin_properties=false\npermissions/access_coarse_location=false\npermissions/access_fine_location=false\npermissions/access_location_extra_commands=false\npermissions/access_mock_location=false\npermissions/access_network_state=false\npermissions/access_surface_flinger=false\npermissions/access_wifi_state=false\npermissions/account_manager=false\npermissions/add_voicemail=false\npermissions/authenticate_accounts=false\npermissions/battery_stats=false\npermissions/bind_accessibility_service=false\npermissions/bind_appwidget=false\npermissions/bind_device_admin=false\npermissions/bind_input_method=false\npermissions/bind_nfc_service=false\npermissions/bind_notification_listener_service=false\npermissions/bind_print_service=false\npermissions/bind_remoteviews=false\npermissions/bind_text_service=false\npermissions/bind_vpn_service=false\npermissions/bind_wallpaper=false\npermissions/bluetooth=false\npermissions/bluetooth_admin=false\npermissions/bluetooth_privileged=false\npermissions/brick=false\npermissions/broadcast_package_removed=false\npermissions/broadcast_sms=false\npermissions/broadcast_sticky=false\npermissions/broadcast_wap_push=false\npermissions/call_phone=false\npermissions/call_privileged=false\npermissions/camera=false\npermissions/capture_audio_output=false\npermissions/capture_secure_video_output=false\npermissions/capture_video_output=false\npermissions/change_component_enabled_state=false\npermissions/change_configuration=false\npermissions/change_network_state=false\npermissions/change_wifi_multicast_state=false\npermissions/change_wifi_state=false\npermissions/clear_app_cache=false\npermissions/clear_app_user_data=false\npermissions/control_location_updates=false\npermissions/delete_cache_files=false\npermissions/delete_packages=false\npermissions/device_power=false\npermissions/diagnostic=false\npermissions/disable_keyguard=false\npermissions/dump=false\npermissions/expand_status_bar=false\npermissions/factory_test=false\npermissions/flashlight=false\npermissions/force_back=false\npermissions/get_accounts=false\npermissions/get_package_size=false\npermissions/get_tasks=false\npermissions/get_top_activity_info=false\npermissions/global_search=false\npermissions/hardware_test=false\npermissions/inject_events=false\npermissions/install_location_provider=false\npermissions/install_packages=false\npermissions/install_shortcut=false\npermissions/internal_system_window=false\npermissions/internet=false\npermissions/kill_background_processes=false\npermissions/location_hardware=false\npermissions/manage_accounts=false\npermissions/manage_app_tokens=false\npermissions/manage_documents=false\npermissions/manage_external_storage=false\npermissions/master_clear=false\npermissions/media_content_control=false\npermissions/modify_audio_settings=false\npermissions/modify_phone_state=false\npermissions/mount_format_filesystems=false\npermissions/mount_unmount_filesystems=false\npermissions/nfc=false\npermissions/persistent_activity=false\npermissions/process_outgoing_calls=false\npermissions/read_calendar=false\npermissions/read_call_log=false\npermissions/read_contacts=false\npermissions/read_external_storage=false\npermissions/read_frame_buffer=false\npermissions/read_history_bookmarks=false\npermissions/read_input_state=false\npermissions/read_logs=false\npermissions/read_phone_state=false\npermissions/read_profile=false\npermissions/read_sms=false\npermissions/read_social_stream=false\npermissions/read_sync_settings=false\npermissions/read_sync_stats=false\npermissions/read_user_dictionary=false\npermissions/reboot=false\npermissions/receive_boot_completed=false\npermissions/receive_mms=false\npermissions/receive_sms=false\npermissions/receive_wap_push=false\npermissions/record_audio=false\npermissions/reorder_tasks=false\npermissions/restart_packages=false\npermissions/send_respond_via_message=false\npermissions/send_sms=false\npermissions/set_activity_watcher=false\npermissions/set_alarm=false\npermissions/set_always_finish=false\npermissions/set_animation_scale=false\npermissions/set_debug_app=false\npermissions/set_orientation=false\npermissions/set_pointer_speed=false\npermissions/set_preferred_applications=false\npermissions/set_process_limit=false\npermissions/set_time=false\npermissions/set_time_zone=false\npermissions/set_wallpaper=false\npermissions/set_wallpaper_hints=false\npermissions/signal_persistent_processes=false\npermissions/status_bar=false\npermissions/subscribed_feeds_read=false\npermissions/subscribed_feeds_write=false\npermissions/system_alert_window=false\npermissions/transmit_ir=false\npermissions/uninstall_shortcut=false\npermissions/update_device_stats=false\npermissions/use_credentials=false\npermissions/use_sip=false\npermissions/vibrate=false\npermissions/wake_lock=false\npermissions/write_apn_settings=false\npermissions/write_calendar=false\npermissions/write_call_log=false\npermissions/write_contacts=false\npermissions/write_external_storage=false\npermissions/write_gservices=false\npermissions/write_history_bookmarks=false\npermissions/write_profile=false\npermissions/write_secure_settings=false\npermissions/write_settings=false\npermissions/write_sms=false\npermissions/write_social_stream=false\npermissions/write_sync_settings=false\npermissions/write_user_dictionary=false\ngraphics/32_bits_framebuffer=true\nxr_features/degrees_of_freedom=0\none_click_deploy/clear_previous_install=false\ncustom_template/use_custom_build=true\nscreen/orientation=0\nscreen/opengl_debug=false\n\n[preset.1]\n\nname=\"Windows Desktop\"\nplatform=\"Windows Desktop\"\nrunnable=true\ndedicated_server=false\ncustom_features=\"\"\nexport_filter=\"all_resources\"\ninclude_filter=\"*.bank, *.ogg\"\nexclude_filter=\"\"\nexport_path=\"../../../GodotFmodExport/FMOD.exe\"\nencryption_include_filters=\"\"\nencryption_exclude_filters=\"\"\nencrypt_pck=false\nencrypt_directory=false\n\n[preset.1.options]\n\ncustom_template/debug=\"\"\ncustom_template/release=\"\"\ndebug/export_console_wrapper=1\nbinary_format/embed_pck=false\ntexture_format/bptc=false\ntexture_format/s3tc=true\ntexture_format/etc=false\ntexture_format/etc2=false\nbinary_format/architecture=\"x86_64\"\ncodesign/enable=false\ncodesign/timestamp=true\ncodesign/timestamp_server_url=\"\"\ncodesign/digest_algorithm=1\ncodesign/description=\"\"\ncodesign/custom_options=PackedStringArray()\napplication/modify_resources=true\napplication/icon=\"\"\napplication/console_wrapper_icon=\"\"\napplication/icon_interpolation=4\napplication/file_version=\"\"\napplication/product_version=\"\"\napplication/company_name=\"\"\napplication/product_name=\"\"\napplication/file_description=\"\"\napplication/copyright=\"\"\napplication/trademarks=\"\"\nssh_remote_deploy/enabled=false\nssh_remote_deploy/host=\"user@host_ip\"\nssh_remote_deploy/port=\"22\"\nssh_remote_deploy/extra_args_ssh=\"\"\nssh_remote_deploy/extra_args_scp=\"\"\nssh_remote_deploy/run_script=\"Expand-Archive -LiteralPath '{temp_dir}\\\\{archive_name}' -DestinationPath '{temp_dir}'\n$action = New-ScheduledTaskAction -Execute '{temp_dir}\\\\{exe_name}' -Argument '{cmd_args}'\n$trigger = New-ScheduledTaskTrigger -Once -At 00:00\n$settings = New-ScheduledTaskSettingsSet\n$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings\nRegister-ScheduledTask godot_remote_debug -InputObject $task -Force:$true\nStart-ScheduledTask -TaskName godot_remote_debug\nwhile (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }\nUnregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue\"\nssh_remote_deploy/cleanup_script=\"Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue\nUnregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue\nRemove-Item -Recurse -Force '{temp_dir}'\"\ntexture_format/no_bptc_fallbacks=true\nbinary_format/64_bits=true\n\n[preset.2]\n\nname=\"iOS\"\nplatform=\"iOS\"\nrunnable=true\ndedicated_server=false\ncustom_features=\"\"\nexport_filter=\"all_resources\"\ninclude_filter=\"\"\nexclude_filter=\"\"\nexport_path=\"../../../../Desktop/fmod-ios-export/FmodTestDemo.ipa\"\nencryption_include_filters=\"\"\nencryption_exclude_filters=\"\"\nencrypt_pck=false\nencrypt_directory=false\n\n[preset.2.options]\n\ncustom_template/debug=\"\"\ncustom_template/release=\"\"\narchitectures/arm64=true\napplication/app_store_team_id=\"changeit\"\napplication/code_sign_identity_debug=\"iPhone Developer\"\napplication/export_method_debug=1\napplication/code_sign_identity_release=\"iPhone Developer\"\napplication/export_method_release=1\napplication/targeted_device_family=2\napplication/bundle_identifier=\"com.utopiarise.godot.fmod\"\napplication/signature=\"????\"\napplication/short_version=\"1.0\"\napplication/version=\"1.0\"\napplication/icon_interpolation=4\napplication/launch_screens_interpolation=4\ncapabilities/access_wifi=false\ncapabilities/push_notifications=false\nuser_data/accessible_from_files_app=false\nuser_data/accessible_from_itunes_sharing=false\nprivacy/camera_usage_description=\"\"\nprivacy/camera_usage_description_localized={}\nprivacy/microphone_usage_description=\"\"\nprivacy/microphone_usage_description_localized={}\nprivacy/photolibrary_usage_description=\"\"\nprivacy/photolibrary_usage_description_localized={}\nicons/iphone_120x120=\"\"\nicons/iphone_180x180=\"\"\nicons/ipad_76x76=\"\"\nicons/ipad_152x152=\"\"\nicons/ipad_167x167=\"\"\nicons/app_store_1024x1024=\"res://appstore.png\"\nicons/spotlight_40x40=\"\"\nicons/spotlight_80x80=\"\"\nicons/settings_58x58=\"\"\nicons/settings_87x87=\"\"\nicons/notification_40x40=\"\"\nicons/notification_60x60=\"\"\nstoryboard/use_launch_screen_storyboard=false\nstoryboard/image_scale_mode=0\nstoryboard/custom_image@2x=\"\"\nstoryboard/custom_image@3x=\"\"\nstoryboard/use_custom_bg_color=false\nstoryboard/custom_bg_color=Color(0, 0, 0, 1)\nlandscape_launch_screens/iphone_2436x1125=\"\"\nlandscape_launch_screens/iphone_2208x1242=\"\"\nlandscape_launch_screens/ipad_1024x768=\"\"\nlandscape_launch_screens/ipad_2048x1536=\"\"\nportrait_launch_screens/iphone_640x960=\"\"\nportrait_launch_screens/iphone_640x1136=\"\"\nportrait_launch_screens/iphone_750x1334=\"\"\nportrait_launch_screens/iphone_1125x2436=\"\"\nportrait_launch_screens/ipad_768x1024=\"\"\nportrait_launch_screens/ipad_1536x2048=\"\"\nportrait_launch_screens/iphone_1242x2208=\"\"\napplication/name=\"\"\napplication/info=\"changeit\"\napplication/identifier=\"changeit\"\napplication/copyright=\"\"\ncapabilities/arkit=false\ncapabilities/camera=false\ncapabilities/game_center=true\ncapabilities/in_app_purchases=false\norientation/portrait=true\norientation/landscape_left=true\norientation/landscape_right=true\norientation/portrait_upside_down=true\nrequired_icons/iphone_120x120=\"res://iOSIcons/Icon-App-60x60@2x.png\"\nrequired_icons/ipad_76x76=\"res://iOSIcons/Icon-App-76x76@1x.png\"\nrequired_icons/app_store_1024x1024=\"res://iOSIcons/ItunesArtwork@2x.png\"\noptional_icons/iphone_180x180=\"res://iOSIcons/Icon-App-60x60@3x.png\"\noptional_icons/ipad_152x152=\"res://iOSIcons/Icon-App-76x76@2x.png\"\noptional_icons/ipad_167x167=\"res://iOSIcons/Icon-App-83.5x83.5@2x.png\"\noptional_icons/spotlight_40x40=\"res://iOSIcons/Icon-App-40x40@1x.png\"\noptional_icons/spotlight_80x80=\"res://iOSIcons/Icon-App-40x40@2x.png\"\narchitectures/armv7=false\n\n[preset.3]\n\nname=\"macOS\"\nplatform=\"macOS\"\nrunnable=true\ndedicated_server=false\ncustom_features=\"\"\nexport_filter=\"all_resources\"\ninclude_filter=\"\"\nexclude_filter=\"\"\nexport_path=\"../../../../Desktop/fmod-macos-export/FmodTestDemo.dmg\"\nencryption_include_filters=\"\"\nencryption_exclude_filters=\"\"\nencrypt_pck=false\nencrypt_directory=false\n\n[preset.3.options]\n\nexport/distribution_type=1\nbinary_format/architecture=\"universal\"\ncustom_template/debug=\"\"\ncustom_template/release=\"\"\ndebug/export_console_wrapper=1\napplication/icon=\"\"\napplication/icon_interpolation=4\napplication/bundle_identifier=\"com.utopiarise.godot.fmod\"\napplication/signature=\"\"\napplication/app_category=\"Games\"\napplication/short_version=\"1.0\"\napplication/version=\"1.0\"\napplication/copyright=\"\"\napplication/copyright_localized={}\napplication/min_macos_version=\"10.12\"\ndisplay/high_res=true\nxcode/platform_build=\"14C18\"\nxcode/sdk_version=\"13.1\"\nxcode/sdk_build=\"22C55\"\nxcode/sdk_name=\"macosx13.1\"\nxcode/xcode_version=\"1420\"\nxcode/xcode_build=\"14C18\"\ncodesign/codesign=3\ncodesign/installer_identity=\"\"\ncodesign/apple_team_id=\"\"\ncodesign/identity=\"\"\ncodesign/entitlements/custom_file=\"\"\ncodesign/entitlements/allow_jit_code_execution=false\ncodesign/entitlements/allow_unsigned_executable_memory=false\ncodesign/entitlements/allow_dyld_environment_variables=false\ncodesign/entitlements/disable_library_validation=false\ncodesign/entitlements/audio_input=false\ncodesign/entitlements/camera=false\ncodesign/entitlements/location=false\ncodesign/entitlements/address_book=false\ncodesign/entitlements/calendars=false\ncodesign/entitlements/photos_library=false\ncodesign/entitlements/apple_events=false\ncodesign/entitlements/debugging=false\ncodesign/entitlements/app_sandbox/enabled=false\ncodesign/entitlements/app_sandbox/network_server=false\ncodesign/entitlements/app_sandbox/network_client=false\ncodesign/entitlements/app_sandbox/device_usb=false\ncodesign/entitlements/app_sandbox/device_bluetooth=false\ncodesign/entitlements/app_sandbox/files_downloads=0\ncodesign/entitlements/app_sandbox/files_pictures=0\ncodesign/entitlements/app_sandbox/files_music=0\ncodesign/entitlements/app_sandbox/files_movies=0\ncodesign/entitlements/app_sandbox/helper_executables=[]\ncodesign/custom_options=PackedStringArray()\nnotarization/notarization=0\nprivacy/microphone_usage_description=\"\"\nprivacy/microphone_usage_description_localized={}\nprivacy/camera_usage_description=\"\"\nprivacy/camera_usage_description_localized={}\nprivacy/location_usage_description=\"\"\nprivacy/location_usage_description_localized={}\nprivacy/address_book_usage_description=\"\"\nprivacy/address_book_usage_description_localized={}\nprivacy/calendar_usage_description=\"\"\nprivacy/calendar_usage_description_localized={}\nprivacy/photos_library_usage_description=\"\"\nprivacy/photos_library_usage_description_localized={}\nprivacy/desktop_folder_usage_description=\"\"\nprivacy/desktop_folder_usage_description_localized={}\nprivacy/documents_folder_usage_description=\"\"\nprivacy/documents_folder_usage_description_localized={}\nprivacy/downloads_folder_usage_description=\"\"\nprivacy/downloads_folder_usage_description_localized={}\nprivacy/network_volumes_usage_description=\"\"\nprivacy/network_volumes_usage_description_localized={}\nprivacy/removable_volumes_usage_description=\"\"\nprivacy/removable_volumes_usage_description_localized={}\nssh_remote_deploy/enabled=false\nssh_remote_deploy/host=\"user@host_ip\"\nssh_remote_deploy/port=\"22\"\nssh_remote_deploy/extra_args_ssh=\"\"\nssh_remote_deploy/extra_args_scp=\"\"\nssh_remote_deploy/run_script=\"#!/usr/bin/env bash\nunzip -o -q \\\"{temp_dir}/{archive_name}\\\" -d \\\"{temp_dir}\\\"\nopen \\\"{temp_dir}/{exe_name}.app\\\" --args {cmd_args}\"\nssh_remote_deploy/cleanup_script=\"#!/usr/bin/env bash\nkill $(pgrep -x -f \\\"{temp_dir}/{exe_name}.app/Contents/MacOS/{exe_name} {cmd_args}\\\")\nrm -rf \\\"{temp_dir}\\\"\"\n"
  },
  {
    "path": "demo/high_level_2D/ChangeColor.gd",
    "content": "extends Area2D\n\nvar event: FmodEvent = null\nvar icon: Sprite2D\n\n# Called when the node enters the scene tree for the first time.\nfunc _ready():\n\tbody_entered.connect(enter)\n\tbody_exited.connect(leave)\n\t$FmodEventEmitter2D.paused = true\n\n# warning-ignore:unused_argument\nfunc enter(_area):\n\tprint(\"enter\")\n\t\n\t$FmodEventEmitter2D.paused = false\n\t\n# warning-ignore:unused_argument\nfunc leave(_area):\n\tprint(\"leave\")\n\t$FmodEventEmitter2D.paused = true\n\n# warning-ignore:unused_argument\nfunc change_color(_dict: Dictionary):\n\t$icon.self_modulate = Color(randf_range(0,1), randf_range(0,1), randf_range(0,1), 1)\n"
  },
  {
    "path": "demo/high_level_2D/ChangeColor.gd.uid",
    "content": "uid://ddkyglwgd83qf\n"
  },
  {
    "path": "demo/high_level_2D/ChooseLanguageButton.gd",
    "content": "class_name ChooseLanguageButton extends OptionButton\n\n\n@export var root_node: Node\nvar current_bank_loader: FmodBankLoader = null\n\nfunc _enter_tree():\n\tconnect(\"item_selected\", _on_item_selected)\n\nfunc _on_item_selected(index: int):\n\tif index == -1:\n\t\treturn\n\tif (current_bank_loader != null):\n\t\troot_node.remove_child(current_bank_loader)\n\tvar bank_path := \"res://assets/Banks/Dialogue_%s.bank\" % get_item_text(index)\n\tcurrent_bank_loader = FmodBankLoader.new()\n\tcurrent_bank_loader.bank_paths = [bank_path]\n\troot_node.add_child(current_bank_loader)\n"
  },
  {
    "path": "demo/high_level_2D/ChooseLanguageButton.gd.uid",
    "content": "uid://dlu46ug37ojmx\n"
  },
  {
    "path": "demo/high_level_2D/Emitter.gd",
    "content": "extends FmodEventEmitter2D\n\nvar isPlaying: bool = true\n\nfunc _process(_delta):\n\tif Input.is_action_just_pressed(\"space\"):\n\t\tisPlaying = !isPlaying\n\t\tif(isPlaying):\n\t\t\tprint(\"Mower playing\")\n\t\t\tpaused = false\n\t\telse:\n\t\t\tprint(\"Mower paused\")\n\t\t\tpaused = true\n\telif Input.is_action_just_pressed(\"kill_event\"):\n\t\tself.queue_free()\n\tif Input.is_action_pressed(\"engine_power_up\"):\n\t\tself[\"fmod_parameters/RPM\"] = self[\"fmod_parameters/RPM\"] + 10\n\tif Input.is_action_pressed(\"engine_power_down\"):\n\t\tself[\"fmod_parameters/RPM\"] = self[\"fmod_parameters/RPM\"] - 10\n"
  },
  {
    "path": "demo/high_level_2D/Emitter.gd.uid",
    "content": "uid://d2j35xjdrcpu0\n"
  },
  {
    "path": "demo/high_level_2D/FmodNodesTest.tscn",
    "content": "[gd_scene load_steps=14 format=3 uid=\"uid://dl6g18ybwc83t\"]\n\n[ext_resource type=\"Script\" uid=\"uid://dbw46rru0a7ab\" path=\"res://high_level_2D/sin_move.gd\" id=\"1_2lkrj\"]\n[ext_resource type=\"Script\" uid=\"uid://d2j35xjdrcpu0\" path=\"res://high_level_2D/Emitter.gd\" id=\"2_5cntr\"]\n[ext_resource type=\"Texture2D\" uid=\"uid://qqnlquxquycf\" path=\"res://icon.png\" id=\"2_llv2n\"]\n[ext_resource type=\"Script\" uid=\"uid://bgrknjkxmlwqw\" path=\"res://high_level_2D/Kinematic.gd\" id=\"3_dlbku\"]\n[ext_resource type=\"Script\" uid=\"uid://ddkyglwgd83qf\" path=\"res://high_level_2D/ChangeColor.gd\" id=\"5_5p5kb\"]\n[ext_resource type=\"Script\" uid=\"uid://dae10jufdshyv\" path=\"res://low_level_2D/EnterAndLeave.gd\" id=\"5_jxvuy\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://glfbseq2tmgg\" path=\"res://high_level_2D/footstep.tscn\" id=\"5_usogy\"]\n[ext_resource type=\"Script\" uid=\"uid://rxnfus6cxfle\" path=\"res://low_level_2D/EnterandLeave2.gd\" id=\"7_c28gt\"]\n[ext_resource type=\"Script\" uid=\"uid://dlu46ug37ojmx\" path=\"res://high_level_2D/ChooseLanguageButton.gd\" id=\"9_syqwk\"]\n[ext_resource type=\"Script\" uid=\"uid://bvjoomad02ag2\" path=\"res://high_level_2D/SayWelcomeButton.gd\" id=\"10_gwsas\"]\n\n[sub_resource type=\"RectangleShape2D\" id=\"1\"]\nsize = Vector2(87.7038, 82.621)\n\n[sub_resource type=\"RectangleShape2D\" id=\"2\"]\nresource_local_to_scene = true\nsize = Vector2(288.124, 291.966)\n\n[sub_resource type=\"RectangleShape2D\" id=\"3\"]\nsize = Vector2(289.938, 284.96)\n\n[node name=\"FmodNodesTest\" type=\"Node\"]\n\n[node name=\"FmodBankLoader\" type=\"FmodBankLoader\" parent=\".\"]\nbank_paths = [\"res://assets/Banks/Master.strings.bank\", \"res://assets/Banks/Master.bank\", \"res://assets/Banks/Music.bank\", \"res://assets/Banks/Vehicles.bank\", \"res://assets/Banks/SFX.bank\"]\n\n[node name=\"Node2D\" type=\"Node2D\" parent=\"FmodBankLoader\"]\nposition = Vector2(703, 486)\nscript = ExtResource(\"1_2lkrj\")\n\n[node name=\"Emitter\" type=\"FmodEventEmitter2D\" parent=\"FmodBankLoader/Node2D\"]\nevent_name = \"event:/Vehicles/Car Engine\"\nevent_guid = \"{0c8363b4-23af-4f9c-af4b-0951bfd37d84}\"\nautoplay = true\nvolume = 2.0\nfmod_parameters/RPM/id = 5864137074015534804\nfmod_parameters/RPM = 600.0\nfmod_parameters/RPM/variant_type = 3\nfmod_parameters/Load/id = -1795603775021193717\nfmod_parameters/Load = -1.0\nfmod_parameters/Load/variant_type = 3\nself_modulate = Color(0.988235, 0, 0, 1)\nposition = Vector2(-3.05176e-05, 0)\nscript = ExtResource(\"2_5cntr\")\n\n[node name=\"Label\" type=\"Label\" parent=\"FmodBankLoader/Node2D\"]\nanchors_preset = 4\nanchor_top = 0.5\nanchor_bottom = 0.5\noffset_left = 49.0\noffset_top = -39.0\noffset_right = 284.0\noffset_bottom = 39.0\ngrow_vertical = 2\nsize_flags_stretch_ratio = 0.0\ntext = \"Press Space to pause/unpause\nCome closer to hear it\nPress Up arrow and down arrow\n to modify engine rpm\"\n\n[node name=\"Sprite\" type=\"Sprite2D\" parent=\"FmodBankLoader/Node2D\"]\nself_modulate = Color(0.988235, 0, 0, 1)\nposition = Vector2(-3.05176e-05, 0)\ntexture = ExtResource(\"2_llv2n\")\n\n[node name=\"Listener\" type=\"CharacterBody2D\" parent=\"FmodBankLoader\"]\nposition = Vector2(500, 150)\nscript = ExtResource(\"3_dlbku\")\nfootstep_scene = ExtResource(\"5_usogy\")\n\n[node name=\"FmodListener2D\" type=\"FmodListener2D\" parent=\"FmodBankLoader/Listener\"]\n\n[node name=\"icon\" type=\"Sprite2D\" parent=\"FmodBankLoader/Listener\"]\nposition = Vector2(1.89996, 4.12415)\ntexture = ExtResource(\"2_llv2n\")\n\n[node name=\"CollisionShape2D\" type=\"CollisionShape2D\" parent=\"FmodBankLoader/Listener\"]\nposition = Vector2(0.440125, 0.440125)\nshape = SubResource(\"1\")\n\n[node name=\"Label\" type=\"Label\" parent=\"FmodBankLoader/Listener\"]\nanchors_preset = 5\nanchor_left = 0.5\nanchor_right = 0.5\ngrow_horizontal = 2\nsize_flags_stretch_ratio = 0.0\ntext = \"Listener\nKill it with K key!\"\n\n[node name=\"SoundArea1\" type=\"Area2D\" parent=\"FmodBankLoader\"]\nposition = Vector2(146.558, 148.68)\nscript = ExtResource(\"5_jxvuy\")\n\n[node name=\"CollisionShape2D\" type=\"CollisionShape2D\" parent=\"FmodBankLoader/SoundArea1\"]\nshape = SubResource(\"2\")\n\n[node name=\"icon\" type=\"Sprite2D\" parent=\"FmodBankLoader/SoundArea1\"]\nself_modulate = Color(0.113725, 0.823529, 0.317647, 1)\nz_index = -1\nscale = Vector2(3, 3)\ntexture = ExtResource(\"2_llv2n\")\n\n[node name=\"Label2\" type=\"Label\" parent=\"FmodBankLoader/SoundArea1\"]\nsize_flags_stretch_ratio = 0.0\ntext = \"Files loaded as sounds.\nPlayed when entering and exiting this area.\nSeveral instances of the same sound can be played at the same time\n\"\n\n[node name=\"SoundArea2\" type=\"Area2D\" parent=\"FmodBankLoader\"]\nposition = Vector2(818.43, 97.5364)\nscript = ExtResource(\"5_5p5kb\")\n\n[node name=\"FmodEventEmitter2D\" type=\"FmodEventEmitter2D\" parent=\"FmodBankLoader/SoundArea2\"]\nevent_name = \"event:/Music/Level 02\"\nevent_guid = \"{c7f946fd-d695-499b-a820-752799c4921d}\"\nautoplay = true\n\n[node name=\"CollisionShape2D\" type=\"CollisionShape2D\" parent=\"FmodBankLoader/SoundArea2\"]\nposition = Vector2(52.8021, 45.8286)\nshape = SubResource(\"3\")\n\n[node name=\"icon\" type=\"Sprite2D\" parent=\"FmodBankLoader/SoundArea2\"]\nself_modulate = Color(0.0117647, 0.956863, 0.0156863, 1)\nz_index = -1\nposition = Vector2(52.3218, 46.0544)\nscale = Vector2(3, 3)\ntexture = ExtResource(\"2_llv2n\")\n\n[node name=\"Label3\" type=\"Label\" parent=\"FmodBankLoader/SoundArea2\"]\nsize_flags_stretch_ratio = 0.0\ntext = \"Event is unpaused when entering \nThe color changes every beat\"\n\n[node name=\"SoundArea3\" type=\"Area2D\" parent=\"FmodBankLoader\"]\nposition = Vector2(91.9974, 414.5)\nscript = ExtResource(\"7_c28gt\")\n\n[node name=\"CollisionShape2D\" type=\"CollisionShape2D\" parent=\"FmodBankLoader/SoundArea3\"]\nposition = Vector2(52.8021, 45.8286)\nshape = SubResource(\"3\")\n\n[node name=\"icon\" type=\"Sprite2D\" parent=\"FmodBankLoader/SoundArea3\"]\nself_modulate = Color(0.827451, 0.345098, 0.0941176, 1)\nz_index = -1\nposition = Vector2(52.3218, 46.0544)\nscale = Vector2(3, 3)\ntexture = ExtResource(\"2_llv2n\")\n\n[node name=\"Label3\" type=\"Label\" parent=\"FmodBankLoader/SoundArea3\"]\nsize_flags_stretch_ratio = 0.0\ntext = \"File loaded as Music\nStart when entering\nStop when exiting\nOnly one instance of that music can be played \"\n\n[node name=\"ChooseLanguageButton\" type=\"OptionButton\" parent=\".\" node_paths=PackedStringArray(\"root_node\")]\noffset_left = 918.0\noffset_top = 615.0\noffset_right = 986.0\noffset_bottom = 646.0\nitem_count = 3\npopup/item_0/text = \"CN\"\npopup/item_0/id = 0\npopup/item_1/text = \"EN\"\npopup/item_1/id = 1\npopup/item_2/text = \"JP\"\npopup/item_2/id = 2\nscript = ExtResource(\"9_syqwk\")\nroot_node = NodePath(\"..\")\n\n[node name=\"SayWelcomeButton\" type=\"Button\" parent=\".\" node_paths=PackedStringArray(\"welcome_option_button\")]\noffset_left = 987.0\noffset_top = 615.0\noffset_right = 1100.0\noffset_bottom = 646.0\ntext = \"Say welcome\"\nscript = ExtResource(\"10_gwsas\")\nwelcome_option_button = NodePath(\"../ChooseLanguageButton\")\n"
  },
  {
    "path": "demo/high_level_2D/Kinematic.gd",
    "content": "extends CharacterBody2D\n\nvar distance_traveled := 0\n@export var footstep_scene: PackedScene\n@onready var foot_step_emitter: FmodEventEmitter2D = footstep_scene.instantiate()\n\nfunc _ready() -> void:\n\tadd_child(foot_step_emitter)\n\nfunc _process(delta):\n\tvar direction: Vector2 = Vector2(0,0)\n\tvar rotation_dir = 0\n\tif Input.is_action_pressed(\"right\"):\n\t\tdirection.x += 1\n\tif Input.is_action_pressed(\"left\"):\n\t\tdirection.x -= 1\n\tif Input.is_action_pressed(\"up\"):\n\t\tdirection.y -= 1\n\tif Input.is_action_pressed(\"down\"):\n\t\tdirection.y += 1\n\tif Input.is_action_pressed(\"rotate_right\"):\n\t\trotation_dir = 1\n\tif Input.is_action_pressed(\"rotate_left\"):\n\t\trotation_dir = -1\n\tif direction != Vector2(0,0):\n\t\tdistance_traveled += delta * 200\n\t\tif distance_traveled >= 35:\n\t\t\tdistance_traveled = 0\n\t\t\tfoot_step_emitter.play_one_shot()\n\tdirection = direction.normalized()\n\tdirection.x = direction.x * delta * 200\n\tdirection.y = direction.y * delta * 200\n\tself.position += direction\n\tself.rotate(rotation_dir * delta * 5)\n\tif Input.is_action_pressed(\"lock_listener\"):\n\t\t$FmodListener2D.is_locked = !$FmodListener2D.is_locked\n\telif Input.is_action_pressed(\"kill\"):\n\t\tself.queue_free()\n"
  },
  {
    "path": "demo/high_level_2D/Kinematic.gd.uid",
    "content": "uid://bgrknjkxmlwqw\n"
  },
  {
    "path": "demo/high_level_2D/SayWelcomeButton.gd",
    "content": "extends Button\n\n\n@export var welcome_option_button: ChooseLanguageButton\n\nfunc _enter_tree():\n\tconnect(\"pressed\", _on_pressed)\n\nfunc _on_pressed():\n\tif welcome_option_button.current_bank_loader == null:\n\t\treturn\n\tvar event_emitter = FmodEventEmitter2D.new()\n\tevent_emitter.event_guid = \"{9aa2ecc5-ea4b-4ebe-85c3-054b11b21dcd}\"\n\tevent_emitter.attached = false\n\tevent_emitter.autoplay = true\n\tevent_emitter.auto_release = true\n\tevent_emitter.set_programmer_callback(\"welcome\")\n\twelcome_option_button.current_bank_loader.add_child(event_emitter)\n"
  },
  {
    "path": "demo/high_level_2D/SayWelcomeButton.gd.uid",
    "content": "uid://bvjoomad02ag2\n"
  },
  {
    "path": "demo/high_level_2D/footstep.tscn",
    "content": "[gd_scene format=3 uid=\"uid://glfbseq2tmgg\"]\n\n[node name=\"Footstep\" type=\"FmodEventEmitter2D\"]\nevent_guid = \"{e905d401-76f5-4741-a885-f7844b244671}\"\nattached = false\nallow_fadeout = false\n"
  },
  {
    "path": "demo/high_level_2D/sin_move.gd",
    "content": "extends Node2D\n\n\nfunc _process(delta: float) -> void:\n\tvar time = Time.get_ticks_msec()/1000.0\n\tself.position.x = 700 + 300 * sin(time)\n"
  },
  {
    "path": "demo/high_level_2D/sin_move.gd.uid",
    "content": "uid://dbw46rru0a7ab\n"
  },
  {
    "path": "demo/high_level_3D/FPSCounter.gd",
    "content": "extends Label\n\nfunc _ready():\n\tset_process(true)\n\t\nfunc _process(_delta: float):\n\tself.text = 'FPS: %s' % Engine.get_frames_per_second()\n"
  },
  {
    "path": "demo/high_level_3D/FPSCounter.gd.uid",
    "content": "uid://dqy6qt50sgnnw\n"
  },
  {
    "path": "demo/high_level_3D/World.tscn",
    "content": "[gd_scene load_steps=13 format=3 uid=\"uid://dk02rm1jcir6t\"]\n\n[ext_resource type=\"Script\" uid=\"uid://dqy6qt50sgnnw\" path=\"res://high_level_3D/FPSCounter.gd\" id=\"1_sjwuc\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://bhw2o0powjnsp\" path=\"res://high_level_3D/environment/Floor.tscn\" id=\"2_kesb6\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://dl8xj04oxmnsb\" path=\"res://high_level_3D/environment/Ball.tscn\" id=\"3_bkia1\"]\n[ext_resource type=\"Script\" uid=\"uid://vfnvt7s745x1\" path=\"res://high_level_3D/environment/sin_move.gd\" id=\"4_ewod2\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://c7isdpd8ykjep\" path=\"res://high_level_3D/environment/Wall.tscn\" id=\"4_jv1x4\"]\n[ext_resource type=\"Script\" uid=\"uid://c2i08gks60jr0\" path=\"res://high_level_3D/environment/soundcollider.gd\" id=\"4_mxf3j\"]\n[ext_resource type=\"Script\" uid=\"uid://drfwohij4miwv\" path=\"res://high_level_3D/selfdestroy.gd\" id=\"4_vlj6k\"]\n[ext_resource type=\"Script\" uid=\"uid://dnmapedhp0cbt\" path=\"res://high_level_3D/rollingball.gd\" id=\"5_d681a\"]\n[ext_resource type=\"PackedScene\" uid=\"uid://bsguup0m8xqxp\" path=\"res://high_level_3D/player/Player.tscn\" id=\"5_i7hmm\"]\n\n[sub_resource type=\"ProceduralSkyMaterial\" id=\"ProceduralSkyMaterial_y252g\"]\n\n[sub_resource type=\"Sky\" id=\"8\"]\nsky_material = SubResource(\"ProceduralSkyMaterial_y252g\")\n\n[sub_resource type=\"Environment\" id=\"7\"]\nbackground_mode = 2\nsky = SubResource(\"8\")\nssao_intensity = 4.0\n\n[node name=\"Node3D\" type=\"Node3D\"]\n\n[node name=\"FmodBankLoader\" type=\"FmodBankLoader\" parent=\".\"]\nbank_paths = [\"res://assets/Banks/Master.strings.bank\", \"res://assets/Banks/Master.bank\", \"res://assets/Banks/Music.bank\", \"res://assets/Banks/Vehicles.bank\", \"res://assets/Banks/SFX.bank\"]\n\n[node name=\"FPSCounter\" type=\"Label\" parent=\".\"]\noffset_right = 40.0\noffset_bottom = 14.0\ntext = \"FPS: 0\"\nscript = ExtResource(\"1_sjwuc\")\n\n[node name=\"Help\" type=\"Label\" parent=\".\"]\noffset_top = 20.0\noffset_right = 106.0\noffset_bottom = 38.0\ntext = \"Space = Jump\nWASD = Move\"\n\n[node name=\"Floor\" parent=\".\" instance=ExtResource(\"2_kesb6\")]\ntransform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.007, -2, -3.715)\n\n[node name=\"Ball1\" parent=\".\" instance=ExtResource(\"3_bkia1\")]\ntransform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 3.2038)\nfreeze = true\nscript = ExtResource(\"4_ewod2\")\n\n[node name=\"FmodEventEmitter3D\" type=\"FmodEventEmitter3D\" parent=\"Ball1\"]\nevent_name = \"event:/Vehicles/Car Engine\"\nevent_guid = \"{0c8363b4-23af-4f9c-af4b-0951bfd37d84}\"\nautoplay = true\nfmod_parameters/RPM/id = 5864137074015534804\nfmod_parameters/RPM = 600.0\nfmod_parameters/RPM/variant_type = 3\nfmod_parameters/Load/id = -1795603775021193717\nfmod_parameters/Load = -1.0\nfmod_parameters/Load/variant_type = 3\nscript = ExtResource(\"4_vlj6k\")\n\n[node name=\"Ball2\" parent=\".\" instance=ExtResource(\"3_bkia1\")]\ntransform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4, -1.07225, -3)\nsleeping = true\n\n[node name=\"FmodEventEmitter3D\" type=\"FmodEventEmitter3D\" parent=\"Ball2\"]\nevent_name = \"event:/Weapons/Explosion\"\nevent_guid = \"{1f687138-e06c-40f5-9bac-57f84bbcedd3}\"\nvolume = 0.5\nfmod_parameters/Size/id = 6419405856426461066\nfmod_parameters/Size = 0.5\nfmod_parameters/Size/variant_type = 3\nfmod_parameters/Distance/id = -6363846794978107960\nfmod_parameters/Distance = 3.40282e+38\nfmod_parameters/Distance/variant_type = 3\nscript = ExtResource(\"4_mxf3j\")\n\n[node name=\"Ball3\" parent=\".\" instance=ExtResource(\"3_bkia1\")]\ntransform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, -1.03226, -3)\n\n[node name=\"FmodEventEmitter3D\" type=\"FmodEventEmitter3D\" parent=\"Ball3\"]\nevent_name = \"event:/Interactables/Barrel Roll\"\nevent_guid = \"{c42c2240-c4b6-42ed-a473-1a47f19945ea}\"\nautoplay = true\nvolume = 0.5\nfmod_parameters/Speed/id = 841507833874797062\nfmod_parameters/Speed = 0.0\nfmod_parameters/Speed/variant_type = 3\nscript = ExtResource(\"5_d681a\")\n\n[node name=\"Wall\" parent=\".\" instance=ExtResource(\"4_jv1x4\")]\ntransform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.665272, -8.1876)\n\n[node name=\"WorldEnvironment\" type=\"WorldEnvironment\" parent=\".\"]\nenvironment = SubResource(\"7\")\n\n[node name=\"Sun\" type=\"DirectionalLight3D\" parent=\"WorldEnvironment\"]\ntransform = Transform3D(-0.5, -0.296198, 0.813798, 0, 0.939693, 0.34202, -0.866025, 0.17101, -0.469847, 0, 0, 0)\nlayers = 262144\nlight_color = Color(0.94902, 0.580392, 0.247059, 1)\nlight_cull_mask = 4294443007\nshadow_enabled = true\n\n[node name=\"Player\" parent=\".\" instance=ExtResource(\"5_i7hmm\")]\ntransform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.47008, 8.47787)\n\n[node name=\"FmodListener3D\" type=\"FmodListener3D\" parent=\"Player\"]\ntransform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.56489, 0)\n"
  },
  {
    "path": "demo/high_level_3D/environment/1x1.png.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://dd1hsqwhgfly3\"\npath.s3tc=\"res://.godot/imported/1x1.png-4d9e3047873d92d90ea5851d12e7ef58.s3tc.ctex\"\npath.etc2=\"res://.godot/imported/1x1.png-4d9e3047873d92d90ea5851d12e7ef58.etc2.ctex\"\nmetadata={\n\"imported_formats\": [\"s3tc_bptc\", \"etc2_astc\"],\n\"vram_texture\": true\n}\n\n[deps]\n\nsource_file=\"res://high_level_3D/environment/1x1.png\"\ndest_files=[\"res://.godot/imported/1x1.png-4d9e3047873d92d90ea5851d12e7ef58.s3tc.ctex\", \"res://.godot/imported/1x1.png-4d9e3047873d92d90ea5851d12e7ef58.etc2.ctex\"]\n\n[params]\n\ncompress/mode=2\ncompress/high_quality=false\ncompress/lossy_quality=0.7\ncompress/uastc_level=0\ncompress/rdo_quality_loss=0.0\ncompress/hdr_compression=1\ncompress/normal_map=0\ncompress/channel_pack=0\nmipmaps/generate=true\nmipmaps/limit=-1\nroughness/mode=0\nroughness/src_normal=\"\"\nprocess/channel_remap/red=0\nprocess/channel_remap/green=1\nprocess/channel_remap/blue=2\nprocess/channel_remap/alpha=3\nprocess/fix_alpha_border=true\nprocess/premult_alpha=false\nprocess/normal_map_invert_y=false\nprocess/hdr_as_srgb=false\nprocess/hdr_clamp_exposure=false\nprocess/size_limit=0\ndetect_3d/compress_to=0\n"
  },
  {
    "path": "demo/high_level_3D/environment/Ball.tscn",
    "content": "[gd_scene load_steps=4 format=3 uid=\"uid://dl8xj04oxmnsb\"]\n\n[ext_resource type=\"Material\" path=\"res://high_level_3D/environment/ball_material.tres\" id=\"1_4hjdv\"]\n\n[sub_resource type=\"SphereShape3D\" id=\"7\"]\n\n[sub_resource type=\"SphereMesh\" id=\"8\"]\nmaterial = ExtResource(\"1_4hjdv\")\n\n[node name=\"Ball\" type=\"RigidBody3D\"]\nlinear_damp_mode = 1\nlinear_damp = 2.0\nangular_damp_mode = 1\nangular_damp = 3.0\n\n[node name=\"CollisionShape3D\" type=\"CollisionShape3D\" parent=\".\"]\nshape = SubResource(\"7\")\n\n[node name=\"MeshInstance3D\" type=\"MeshInstance3D\" parent=\".\"]\nmesh = SubResource(\"8\")\n"
  },
  {
    "path": "demo/high_level_3D/environment/Floor.tscn",
    "content": "[gd_scene load_steps=4 format=3 uid=\"uid://bhw2o0powjnsp\"]\n\n[ext_resource type=\"Material\" path=\"res://high_level_3D/environment/wall_material.tres\" id=\"1_2s467\"]\n\n[sub_resource type=\"BoxMesh\" id=\"4\"]\nmaterial = ExtResource(\"1_2s467\")\n\n[sub_resource type=\"BoxShape3D\" id=\"3\"]\nsize = Vector3(64, 1, 64)\n\n[node name=\"Floor\" type=\"StaticBody3D\"]\ntransform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.0364, 0)\n\n[node name=\"Mesh\" type=\"MeshInstance3D\" parent=\".\"]\ntransform = Transform3D(64, 0, 0, 0, 1, 0, 0, 0, 64, 0, 0, 0)\nmesh = SubResource(\"4\")\nskeleton = NodePath(\"../..\")\n\n[node name=\"CollisionShape3D\" type=\"CollisionShape3D\" parent=\".\"]\nshape = SubResource(\"3\")\n"
  },
  {
    "path": "demo/high_level_3D/environment/Wall.tscn",
    "content": "[gd_scene load_steps=8 format=3 uid=\"uid://c7isdpd8ykjep\"]\n\n[ext_resource type=\"Material\" path=\"res://high_level_3D/environment/wall_material.tres\" id=\"2\"]\n\n[sub_resource type=\"BoxMesh\" id=\"9\"]\nmaterial = ExtResource(\"2\")\nsize = Vector3(20, 5, 5)\n\n[sub_resource type=\"PrismMesh\" id=\"12\"]\nmaterial = ExtResource(\"2\")\nleft_to_right = 0.0\nsize = Vector3(10, 5, 5)\n\n[sub_resource type=\"ConcavePolygonShape3D\" id=\"15\"]\ndata = PackedVector3Array(-5, 2.5, 2.5, 5, -2.5, 2.5, -5, -2.5, 2.5, -5, 2.5, -2.5, -5, -2.5, -2.5, 5, -2.5, -2.5, -5, 2.5, 2.5, -5, 2.5, -2.5, 5, -2.5, 2.5, -5, 2.5, -2.5, 5, -2.5, -2.5, 5, -2.5, 2.5, -5, 2.5, -2.5, -5, 2.5, 2.5, -5, -2.5, -2.5, -5, 2.5, 2.5, -5, -2.5, 2.5, -5, -2.5, -2.5, -5, -2.5, 2.5, 5, -2.5, 2.5, -5, -2.5, -2.5, 5, -2.5, 2.5, 5, -2.5, -2.5, -5, -2.5, -2.5)\n\n[sub_resource type=\"PrismMesh\" id=\"14\"]\nmaterial = ExtResource(\"2\")\nleft_to_right = 1.0\nsize = Vector3(10, 5, 5)\n\n[sub_resource type=\"ConcavePolygonShape3D\" id=\"16\"]\ndata = PackedVector3Array(5, 2.5, 2.5, 5, -2.5, 2.5, -5, -2.5, 2.5, 5, 2.5, -2.5, -5, -2.5, -2.5, 5, -2.5, -2.5, 5, 2.5, 2.5, 5, 2.5, -2.5, 5, -2.5, 2.5, 5, 2.5, -2.5, 5, -2.5, -2.5, 5, -2.5, 2.5, 5, 2.5, -2.5, 5, 2.5, 2.5, -5, -2.5, -2.5, 5, 2.5, 2.5, -5, -2.5, 2.5, -5, -2.5, -2.5, -5, -2.5, 2.5, 5, -2.5, 2.5, -5, -2.5, -2.5, 5, -2.5, 2.5, 5, -2.5, -2.5, -5, -2.5, -2.5)\n\n[sub_resource type=\"ConcavePolygonShape3D\" id=\"17\"]\ndata = PackedVector3Array(-10, 2.5, 2.5, 10, 2.5, 2.5, -10, -2.5, 2.5, 10, 2.5, 2.5, 10, -2.5, 2.5, -10, -2.5, 2.5, 10, 2.5, -2.5, -10, 2.5, -2.5, 10, -2.5, -2.5, -10, 2.5, -2.5, -10, -2.5, -2.5, 10, -2.5, -2.5, 10, 2.5, 2.5, 10, 2.5, -2.5, 10, -2.5, 2.5, 10, 2.5, -2.5, 10, -2.5, -2.5, 10, -2.5, 2.5, -10, 2.5, -2.5, -10, 2.5, 2.5, -10, -2.5, -2.5, -10, 2.5, 2.5, -10, -2.5, 2.5, -10, -2.5, -2.5, 10, 2.5, 2.5, -10, 2.5, 2.5, 10, 2.5, -2.5, -10, 2.5, 2.5, -10, 2.5, -2.5, 10, 2.5, -2.5, -10, -2.5, 2.5, 10, -2.5, 2.5, -10, -2.5, -2.5, 10, -2.5, 2.5, 10, -2.5, -2.5, -10, -2.5, -2.5)\n\n[node name=\"Wall\" type=\"MeshInstance3D\"]\ntransform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.35862, -5.6476)\nmesh = SubResource(\"9\")\n\n[node name=\"MeshInstance3D\" type=\"MeshInstance3D\" parent=\".\"]\ntransform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15, 0, 0)\nmesh = SubResource(\"12\")\n\n[node name=\"StaticBody3D\" type=\"StaticBody3D\" parent=\"MeshInstance3D\"]\n\n[node name=\"CollisionShape3D\" type=\"CollisionShape3D\" parent=\"MeshInstance3D/StaticBody3D\"]\nshape = SubResource(\"15\")\n\n[node name=\"MeshInstance2\" type=\"MeshInstance3D\" parent=\".\"]\ntransform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0, 0)\nmesh = SubResource(\"14\")\n\n[node name=\"StaticBody3D\" type=\"StaticBody3D\" parent=\"MeshInstance2\"]\n\n[node name=\"CollisionShape3D\" type=\"CollisionShape3D\" parent=\"MeshInstance2/StaticBody3D\"]\nshape = SubResource(\"16\")\n\n[node name=\"StaticBody3D\" type=\"StaticBody3D\" parent=\".\"]\n\n[node name=\"CollisionShape3D\" type=\"CollisionShape3D\" parent=\"StaticBody3D\"]\nshape = SubResource(\"17\")\n"
  },
  {
    "path": "demo/high_level_3D/environment/ball_material.tres",
    "content": "[gd_resource type=\"StandardMaterial3D\" load_steps=2 format=3]\n\n[ext_resource type=\"Texture2D\" uid=\"uid://b8p3qvgwqt8dd\" path=\"res://high_level_3D/environment/1x1.png\" id=\"1\"]\n\n\n[resource]\ndetail_enabled = true\ndetail_blend_mode = 0\ndetail_uv_layer = 0\ndetail_albedo = ExtResource( 1 )\nuv1_scale = Vector3( 0.5, 0.5, 0.5 )\n"
  },
  {
    "path": "demo/high_level_3D/environment/box.tscn",
    "content": "[gd_scene load_steps=4 format=3 uid=\"uid://cnhrhyfu1hs1t\"]\n\n[ext_resource type=\"Material\" path=\"res://high_level_3D/environment/wall_material.tres\" id=\"1_uit7m\"]\n\n[sub_resource type=\"BoxMesh\" id=\"BoxMesh_wnaer\"]\nmaterial = ExtResource(\"1_uit7m\")\nsize = Vector3(0.3, 2, 2)\n\n[sub_resource type=\"ConvexPolygonShape3D\" id=\"ConvexPolygonShape3D_1j8j0\"]\npoints = PackedVector3Array(0.15, 1, 1, -0.15, 1, 1, 0.15, -1, 1, 0.15, 1, -1, -0.15, 1, -1, -0.15, -1, 1, 0.15, -1, -1, -0.15, -1, -1)\n\n[node name=\"Box\" type=\"RigidBody3D\"]\nmass = 4.0\n\n[node name=\"MeshInstance3D\" type=\"MeshInstance3D\" parent=\".\"]\nmesh = SubResource(\"BoxMesh_wnaer\")\n\n[node name=\"CollisionShape3D\" type=\"CollisionShape3D\" parent=\".\"]\nshape = SubResource(\"ConvexPolygonShape3D_1j8j0\")\n"
  },
  {
    "path": "demo/high_level_3D/environment/sin_move.gd",
    "content": "extends RigidBody3D\n\nfunc _process(delta: float) -> void:\n\tvar time = Time.get_ticks_msec()/1000.0\n\tself.position.x = 10 * sin(time)\n"
  },
  {
    "path": "demo/high_level_3D/environment/sin_move.gd.uid",
    "content": "uid://vfnvt7s745x1\n"
  },
  {
    "path": "demo/high_level_3D/environment/soundcollider.gd",
    "content": "extends FmodEventEmitter3D\n\nvar moving := false\n\nfunc _process(_delta: float):\n\n\tvar parent: RigidBody3D = get_parent()\n\tif moving == false and parent.linear_velocity.length() > 1:\n\t\tmoving = true\n\t\tself.play()\n\telif parent.linear_velocity.length() < 1:\n\t\tmoving = false\n"
  },
  {
    "path": "demo/high_level_3D/environment/soundcollider.gd.uid",
    "content": "uid://c2i08gks60jr0\n"
  },
  {
    "path": "demo/high_level_3D/environment/wall_material.tres",
    "content": "[gd_resource type=\"StandardMaterial3D\" load_steps=2 format=3]\n\n[ext_resource type=\"Texture2D\" uid=\"uid://b8p3qvgwqt8dd\" path=\"res://high_level_3D/environment/1x1.png\" id=\"1\"]\n\n\n[resource]\ndetail_enabled = true\ndetail_blend_mode = 0\ndetail_uv_layer = 0\ndetail_albedo = ExtResource( 1 )\nuv1_scale = Vector3( 20, 15, 0 )\n"
  },
  {
    "path": "demo/high_level_3D/player/Camera.gd",
    "content": "extends Camera3D\n\n@onready var Player = get_parent()\n\n## Increase this value to give a slower turn speed\nconst CAMERA_TURN_SPEED = 200\n\nfunc _ready():\n\t## Tell Godot that we want to handle input\n\tset_process_input(true)\n\nfunc look_updown_rotation(new_rotation = 0):\n\t\"\"\"\n\tReturns a new Vector3 which contains only the x direction\n\tWe'll use this vector to compute the final 3D rotation later\n\t\"\"\"\n\tvar toReturn = self.get_rotation() + Vector3(new_rotation, 0, 0)\n\n\t##\n\t## We don't want the player to be able to bend over backwards\n\t## neither to be able to look under their arse.\n\t## Here we'll clamp the vertical look to 90° up and down\n\ttoReturn.x = clamp(toReturn.x, PI / -2, PI / 2)\n\n\treturn toReturn\n\nfunc look_leftright_rotation(new_rotation = 0):\n\t\"\"\"\n\tReturns a new Vector3 which contains only the y direction\n\tWe'll use this vector to compute the final 3D rotation later\n\t\"\"\"\n\treturn Player.get_rotation() + Vector3(0, new_rotation, 0)\n\nfunc _input(event):\n\t\"\"\"\n\tFirst person camera controls\n\t\"\"\"\n\t##\n\t## We'll only process mouse motion events\n\tif not event is InputEventMouseMotion:\n\t\treturn\n\n\t##\n\t## We'll use the parent node \"Player\" to set our left-right rotation\n\t## This prevents us from adding the x-rotation to the y-rotation\n\t## which would result in a kind of flight-simulator camera\n\tPlayer.set_rotation(look_leftright_rotation(event.relative.x / -CAMERA_TURN_SPEED))\n\n\t##\n\t## Now we can simply set our y-rotation for the camera, and let godot\n\t## handle the transformation of both together\n\tself.set_rotation(look_updown_rotation(event.relative.y / -CAMERA_TURN_SPEED))\n\nfunc _enter_tree():\n\t\"\"\"\n\tHide the mouse when we start\n\t\"\"\"\n\tInput.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)\n\tInput.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)\n\nfunc _leave_tree():\n\t\"\"\"\n\tShow the mouse when we leave\n\t\"\"\"\n\tInput.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)\n"
  },
  {
    "path": "demo/high_level_3D/player/Camera.gd.uid",
    "content": "uid://brjp8swvid6no\n"
  },
  {
    "path": "demo/high_level_3D/player/Player.gd",
    "content": "extends CharacterBody3D\n\nvar direction := Vector3(0, 0, 0) # Used for animation\nvar will_jump := false\n\nconst JUMP := 4\nconst PLAYER_MOVE_SPEED := 4\n\n@onready var Camera = $Camera3D\n@onready var GRAVITY = ProjectSettings.get(\"physics/3d/default_gravity\") / 1000\n\nfunc do_jump() -> void:\n\tif not self.is_on_floor():\n\t\treturn\n\n\tif self.will_jump:\n\t\treturn\n\twill_jump = true\n\t\n\tawait get_tree().create_timer(0.05).timeout\n\n\tself.velocity.y += JUMP\n\n\tawait get_tree().create_timer(0.1).timeout\n\twill_jump = false\n\n\nfunc _process(_delta: float) -> void:\n\t\"\"\"\n\tAllow the player to move the camera with WASD\n\tSee Project settings -> Input map for keyboard bindings\n\t\"\"\" \n\tif Input.is_action_just_pressed(\"space\"):\n\t\tself.do_jump()\n\n\tvar amount: float = 1\n\n\tif not is_on_floor() or will_jump:\n\t\tamount = 0.2\n\n\tif Input.is_action_pressed(\"up\"):\n\t\tself.direction.z -= amount\n\n\telif Input.is_action_pressed(\"down\"):\n\t\tself.direction.z += amount\n\n\tif Input.is_action_pressed(\"left\"):\n\t\tself.direction.x -= amount\n\n\telif Input.is_action_pressed(\"right\"):\n\t\tself.direction.x += amount\n\n\tself.direction = self.direction.clamp(Vector3(-1, -1, -1), Vector3(1, 1, 1))\n\n\nfunc _physics_process(delta: float) -> void:\n\t# Apply friction\n\tif self.is_on_floor():\n\t\tself.direction *= Vector3.ONE - Vector3(0.9, 1.0, 0.9) * (10 * delta)\n\n\t# Preserve the Y velocity from the previous frame\n\tself.velocity = Vector3(0, self.velocity.y, 0)\n\n\t# Always add velocity even when we're in the air\n\tself.velocity += get_transform().basis.x * direction.x * PLAYER_MOVE_SPEED\n\tself.velocity += get_transform().basis.z * direction.z * PLAYER_MOVE_SPEED\n\n\t# Apply less gravity if we were on the floor last frame\n\t# This helps our KinematicBody to avoid physics jitter\n\tif self.is_on_floor():\n\t\tself.velocity -= Vector3(0, GRAVITY / 100, 0)\n\telse:\n\t\tself.velocity -= Vector3(0, GRAVITY, 0)\n\n\tself.move_and_slide()\n\n\tfor i in get_slide_collision_count():\n\t\tvar collision = get_slide_collision(i)\n\t\tvar collider = collision.get_collider()\n\t\tif not collider is RigidBody3D:\n\t\t\tcontinue\n\n\t\tcollider.apply_central_impulse(-collision.get_normal() * 0.8)\n\t\tcollider.apply_impulse(-collision.get_normal() * 0.01, collision.get_position())\n"
  },
  {
    "path": "demo/high_level_3D/player/Player.gd.uid",
    "content": "uid://b0wpksi2hoyfn\n"
  },
  {
    "path": "demo/high_level_3D/player/Player.tscn",
    "content": "[gd_scene load_steps=4 format=3 uid=\"uid://bsguup0m8xqxp\"]\n\n[ext_resource type=\"Script\" uid=\"uid://b0wpksi2hoyfn\" path=\"res://high_level_3D/player/Player.gd\" id=\"1\"]\n[ext_resource type=\"Script\" uid=\"uid://brjp8swvid6no\" path=\"res://high_level_3D/player/Camera.gd\" id=\"2_fstpc\"]\n\n[sub_resource type=\"CapsuleShape3D\" id=\"CapsuleShape3D_di3pi\"]\n\n[node name=\"Player\" type=\"CharacterBody3D\"]\nplatform_on_leave = 2\nscript = ExtResource(\"1\")\n\n[node name=\"Camera3D\" type=\"Camera3D\" parent=\".\"]\ntransform = Transform3D(1, 9.09495e-15, 0, -9.09495e-15, 1, 0, 0, 0, 1, -9.31323e-10, 1.67742, 0.13534)\ncull_mask = 524287\ncurrent = true\nfov = 50.0\nfar = 200.0\nscript = ExtResource(\"2_fstpc\")\n\n[node name=\"CollisionShape3D\" type=\"CollisionShape3D\" parent=\".\"]\ntransform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0151734, 1.01978, 0.234704)\nshape = SubResource(\"CapsuleShape3D_di3pi\")\n"
  },
  {
    "path": "demo/high_level_3D/rollingball.gd",
    "content": "extends FmodEventEmitter3D\n\n\nfunc _process(_delta: float):\n\n\tvar parent: RigidBody3D = get_parent()\n\tvar value:= parent.angular_velocity.length() / 2\n\tif value < 0.1:\n\t\tvalue = 0\n\tself[\"fmod_parameters/Speed\"] = value\n"
  },
  {
    "path": "demo/high_level_3D/rollingball.gd.uid",
    "content": "uid://dnmapedhp0cbt\n"
  },
  {
    "path": "demo/high_level_3D/selfdestroy.gd",
    "content": "extends FmodEventEmitter3D\n\n\n\nfunc _process(delta: float) -> void:\n\tif Input.is_action_just_pressed(\"kill\"):\n\t\tself.queue_free()\n"
  },
  {
    "path": "demo/high_level_3D/selfdestroy.gd.uid",
    "content": "uid://drfwohij4miwv\n"
  },
  {
    "path": "demo/icon.png.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://qqnlquxquycf\"\npath=\"res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://icon.png\"\ndest_files=[\"res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex\"]\n\n[params]\n\ncompress/mode=0\ncompress/high_quality=false\ncompress/lossy_quality=0.7\ncompress/uastc_level=0\ncompress/rdo_quality_loss=0.0\ncompress/hdr_compression=1\ncompress/normal_map=0\ncompress/channel_pack=0\nmipmaps/generate=false\nmipmaps/limit=-1\nroughness/mode=0\nroughness/src_normal=\"\"\nprocess/channel_remap/red=0\nprocess/channel_remap/green=1\nprocess/channel_remap/blue=2\nprocess/channel_remap/alpha=3\nprocess/fix_alpha_border=true\nprocess/premult_alpha=false\nprocess/normal_map_invert_y=false\nprocess/hdr_as_srgb=false\nprocess/hdr_clamp_exposure=false\nprocess/size_limit=0\ndetect_3d/compress_to=1\n"
  },
  {
    "path": "demo/icon.svg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://cih2yjmtjoohg\"\npath=\"res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://icon.svg\"\ndest_files=[\"res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex\"]\n\n[params]\n\ncompress/mode=0\ncompress/high_quality=false\ncompress/lossy_quality=0.7\ncompress/uastc_level=0\ncompress/rdo_quality_loss=0.0\ncompress/hdr_compression=1\ncompress/normal_map=0\ncompress/channel_pack=0\nmipmaps/generate=false\nmipmaps/limit=-1\nroughness/mode=0\nroughness/src_normal=\"\"\nprocess/channel_remap/red=0\nprocess/channel_remap/green=1\nprocess/channel_remap/blue=2\nprocess/channel_remap/alpha=3\nprocess/fix_alpha_border=true\nprocess/premult_alpha=false\nprocess/normal_map_invert_y=false\nprocess/hdr_as_srgb=false\nprocess/hdr_clamp_exposure=false\nprocess/size_limit=0\ndetect_3d/compress_to=1\nsvg/scale=1.0\neditor/scale_with_editor_scale=false\neditor/convert_colors_with_editor_theme=false\n"
  },
  {
    "path": "demo/low_level_2D/ChangeColor.gd",
    "content": "extends Area2D\n\nvar event: FmodEvent = null\nvar icon: Sprite2D\n\n# Called when the node enters the scene tree for the first time.\nfunc _ready():\n\tevent = FmodServer.create_event_instance(\"event:/Music/Level 02\")\n\tevent.set_callback(Callable(self, \"change_color\"), FmodServer.FMOD_STUDIO_EVENT_CALLBACK_ALL)\n\tbody_entered.connect(enter)\n\tbody_exited.connect(leave)\n\tevent.start()\n\tevent.set_paused(true)\n\n# warning-ignore:unused_argument\nfunc enter(_area):\n\tprint(\"enter\")\n\tevent.set_paused(false)\n\t\n# warning-ignore:unused_argument\nfunc leave(_area):\n\tprint(\"leave\")\n\tevent.set_paused(true)\n\n# warning-ignore:unused_argument\nfunc change_color(_dict: Dictionary, type: int):\n\tif type == FmodServer.FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_BEAT:\n\t\t$icon.self_modulate = Color(randf_range(0,1), randf_range(0,1), randf_range(0,1), 1)\n"
  },
  {
    "path": "demo/low_level_2D/ChangeColor.gd.uid",
    "content": "uid://be1jwed20nkjr\n"
  },
  {
    "path": "demo/low_level_2D/Emitter.gd",
    "content": "extends Sprite2D\n\nvar isPlaying: bool = true\nvar event: FmodEvent = null\n\n# Called when the node enters the scene tree for the first time.\nfunc _ready():\n\tevent = FmodServer.create_event_instance(\"event:/Vehicles/Car Engine\")\n\tevent.set_2d_attributes(self.global_transform)\n\tevent.set_parameter_by_name(\"RPM\", 600)\n\tevent.set_volume( 2)\n\tevent.start()\n\t\n# warning-ignore:unused_argument\nfunc _process(_delta):\n\tif Input.is_action_just_pressed(\"space\"):\n\t\tisPlaying = !isPlaying\n\t\tif(isPlaying):\n\t\t\tprint(\"Mower playing\")\n\t\t\tevent.set_paused(false)\n\t\telse:\n\t\t\tprint(\"Mower paused\")\n\t\t\tevent.set_paused(true)\n\telif Input.is_action_just_pressed(\"kill_event\"):\n\t\tself.queue_free()\n\tvar time = Time.get_ticks_msec()/1000.0\n\tself.position.x = 300 * sin(time)\n\tevent.set_2d_attributes(self.global_transform)\n"
  },
  {
    "path": "demo/low_level_2D/Emitter.gd.uid",
    "content": "uid://cxg2l03odbmwv\n"
  },
  {
    "path": "demo/low_level_2D/EnterAndLeave.gd",
    "content": "extends Area2D\n\n# Declare member variables here. Examples:\n# var a = 2\nvar open_sound: FmodSound = null\nvar close_sound: FmodSound = null\n\n# Called when the node enters the scene tree for the first time.\nfunc _ready():\n\tFmodServer.load_file_as_sound(\"res://assets/Sounds/doorOpen_1.ogg\")\n\tFmodServer.load_file_as_sound(\"res://assets/Sounds/doorClose_1.ogg\")\n# warning-ignore:return_value_discarded\n\tbody_entered.connect(enter)\n# warning-ignore:return_value_discarded\n\tbody_exited.connect(leave)\n\n# warning-ignore:unused_argument\nfunc enter(_area):\n\tprint(\"enter\")\n\topen_sound = FmodServer.create_sound_instance(\"res://assets/Sounds/doorOpen_1.ogg\")\n\topen_sound.set_pitch(randf_range(0.75,1.25))\n\topen_sound.play()\n\t\n# warning-ignore:unused_argument\nfunc leave(_area):\n\tprint(\"leave\")\n\tclose_sound = FmodServer.create_sound_instance(\"res://assets/Sounds/doorClose_1.ogg\")\n\tclose_sound.set_pitch(randf_range(0.75,1.5))\n\tclose_sound.play()\n\t\n\nfunc _exit_tree():\n\tFmodServer.unload_file(\"res://assets/Sounds/doorOpen_1.ogg\")\n\tFmodServer.unload_file(\"res://assets/Sounds/doorClose_1.ogg\")\n"
  },
  {
    "path": "demo/low_level_2D/EnterAndLeave.gd.uid",
    "content": "uid://dae10jufdshyv\n"
  },
  {
    "path": "demo/low_level_2D/EnterandLeave2.gd",
    "content": "extends Area2D\n\n# Declare member variables here. Examples:\n# var a = 2\nvar music: FmodSound = null\n\n# Called when the node enters the scene tree for the first time.\nfunc _ready():\n\tFmodServer.load_file_as_music(\"res://assets/Music/jingles_SAX07.ogg\")\n# warning-ignore:return_value_discarded\n\tbody_entered.connect(enter)\n# warning-ignore:return_value_discarded\n\tbody_exited.connect(leave)\n\n# warning-ignore:unused_argument\nfunc enter(_area):\n\tprint(\"enter\")\n\tmusic = FmodServer.create_sound_instance(\"res://assets/Music/jingles_SAX07.ogg\")\n\tmusic.play()\n\t\n# warning-ignore:unused_argument\nfunc leave(_area):\n\tprint(\"leave\")\n\tmusic.release()\n\nfunc _exit_tree():\n\tFmodServer.unload_file(\"res://assets/Music/jingles_SAX07.ogg\")\n"
  },
  {
    "path": "demo/low_level_2D/EnterandLeave2.gd.uid",
    "content": "uid://rxnfus6cxfle\n"
  },
  {
    "path": "demo/low_level_2D/FmodScriptTest.tscn",
    "content": "[gd_scene load_steps=13 format=3 uid=\"uid://cs8nm6h12whh1\"]\n\n[ext_resource type=\"Script\" uid=\"uid://dn0b4rle1712\" path=\"res://low_level_2D/FmodTest.gd\" id=\"1_oc8v3\"]\n[ext_resource type=\"Texture2D\" uid=\"uid://qqnlquxquycf\" path=\"res://icon.png\" id=\"2_mamb7\"]\n[ext_resource type=\"Script\" uid=\"uid://cxg2l03odbmwv\" path=\"res://low_level_2D/Emitter.gd\" id=\"3_fx7d3\"]\n[ext_resource type=\"Script\" uid=\"uid://c4p1w0xlxjs4l\" path=\"res://low_level_2D/Listener.gd\" id=\"4_448uv\"]\n[ext_resource type=\"Script\" uid=\"uid://dae10jufdshyv\" path=\"res://low_level_2D/EnterAndLeave.gd\" id=\"5_85yno\"]\n[ext_resource type=\"Script\" uid=\"uid://be1jwed20nkjr\" path=\"res://low_level_2D/ChangeColor.gd\" id=\"6_fyoq1\"]\n[ext_resource type=\"Script\" uid=\"uid://rxnfus6cxfle\" path=\"res://low_level_2D/EnterandLeave2.gd\" id=\"7_6vajh\"]\n[ext_resource type=\"Script\" uid=\"uid://bd3konietc7md\" path=\"res://low_level_2D/LangChooseButton.gd\" id=\"8_f6f5a\"]\n[ext_resource type=\"Script\" uid=\"uid://cpd21piypux6c\" path=\"res://low_level_2D/WelcomeButton.gd\" id=\"9_jo4tj\"]\n\n[sub_resource type=\"RectangleShape2D\" id=\"1\"]\nsize = Vector2(87.7038, 82.621)\n\n[sub_resource type=\"RectangleShape2D\" id=\"2\"]\nresource_local_to_scene = true\nsize = Vector2(288.124, 291.966)\n\n[sub_resource type=\"RectangleShape2D\" id=\"3\"]\nsize = Vector2(289.938, 284.96)\n\n[node name=\"FmodTest\" type=\"Node2D\"]\nscript = ExtResource(\"1_oc8v3\")\n\n[node name=\"Node2D\" type=\"Node2D\" parent=\".\"]\nposition = Vector2(691, 495)\n\n[node name=\"Emitter\" type=\"Sprite2D\" parent=\"Node2D\"]\nself_modulate = Color(0.988235, 0, 0, 1)\nposition = Vector2(-3.05176e-05, 0)\ntexture = ExtResource(\"2_mamb7\")\nscript = ExtResource(\"3_fx7d3\")\n\n[node name=\"Label\" type=\"Label\" parent=\"Node2D\"]\nanchors_preset = 4\nanchor_top = 0.5\nanchor_bottom = 0.5\ngrow_vertical = 2\nsize_flags_stretch_ratio = 0.0\ntext = \"Press Space to pause/unpause\nCome closer to hear it\"\n\n[node name=\"Listener\" type=\"CharacterBody2D\" parent=\".\"]\nposition = Vector2(500, 150)\nscript = ExtResource(\"4_448uv\")\n\n[node name=\"icon\" type=\"Sprite2D\" parent=\"Listener\"]\nposition = Vector2(1.89996, 4.12415)\ntexture = ExtResource(\"2_mamb7\")\n\n[node name=\"CollisionShape2D\" type=\"CollisionShape2D\" parent=\"Listener\"]\nposition = Vector2(0.440125, 0.440125)\nshape = SubResource(\"1\")\n\n[node name=\"Label\" type=\"Label\" parent=\"Listener\"]\nanchors_preset = 5\nanchor_left = 0.5\nanchor_right = 0.5\ngrow_horizontal = 2\nsize_flags_stretch_ratio = 0.0\ntext = \"Listener\nKill it with K key!\"\n\n[node name=\"SoundArea1\" type=\"Area2D\" parent=\".\"]\nposition = Vector2(146.558, 148.68)\nscript = ExtResource(\"5_85yno\")\n\n[node name=\"CollisionShape2D\" type=\"CollisionShape2D\" parent=\"SoundArea1\"]\nshape = SubResource(\"2\")\n\n[node name=\"icon\" type=\"Sprite2D\" parent=\"SoundArea1\"]\nself_modulate = Color(0.113725, 0.823529, 0.317647, 1)\nz_index = -1\nscale = Vector2(3, 3)\ntexture = ExtResource(\"2_mamb7\")\n\n[node name=\"Label2\" type=\"Label\" parent=\"SoundArea1\"]\nsize_flags_stretch_ratio = 0.0\ntext = \"Files loaded as sounds.\nPlayed when entering and exiting this area.\nSeveral instances of the same sound can be played at the same time\n\"\n\n[node name=\"SoundArea2\" type=\"Area2D\" parent=\".\"]\nposition = Vector2(948, 98)\nscript = ExtResource(\"6_fyoq1\")\n\n[node name=\"CollisionShape2D\" type=\"CollisionShape2D\" parent=\"SoundArea2\"]\nposition = Vector2(52.8021, 45.8286)\nshape = SubResource(\"3\")\n\n[node name=\"icon\" type=\"Sprite2D\" parent=\"SoundArea2\"]\nself_modulate = Color(0.0117647, 0.956863, 0.0156863, 1)\nz_index = -1\nposition = Vector2(52.3218, 46.0544)\nscale = Vector2(3, 3)\ntexture = ExtResource(\"2_mamb7\")\n\n[node name=\"Label3\" type=\"Label\" parent=\"SoundArea2\"]\nsize_flags_stretch_ratio = 0.0\ntext = \"Event is unpaused when entering \nThe color changes every beat\"\n\n[node name=\"SoundArea3\" type=\"Area2D\" parent=\".\"]\nposition = Vector2(91.9974, 414.5)\nscript = ExtResource(\"7_6vajh\")\n\n[node name=\"CollisionShape2D\" type=\"CollisionShape2D\" parent=\"SoundArea3\"]\nposition = Vector2(52.8021, 45.8286)\nshape = SubResource(\"3\")\n\n[node name=\"icon\" type=\"Sprite2D\" parent=\"SoundArea3\"]\nself_modulate = Color(0.827451, 0.345098, 0.0941176, 1)\nz_index = -1\nposition = Vector2(52.3218, 46.0544)\nscale = Vector2(3, 3)\ntexture = ExtResource(\"2_mamb7\")\n\n[node name=\"Label3\" type=\"Label\" parent=\"SoundArea3\"]\nsize_flags_stretch_ratio = 0.0\ntext = \"File loaded as Music\nStart when entering\nStop when exiting\nOnly one instance of that music can be played \"\n\n[node name=\"WelcomeOptionButton\" type=\"OptionButton\" parent=\".\"]\noffset_left = 918.0\noffset_top = 615.0\noffset_right = 986.0\noffset_bottom = 646.0\nitem_count = 3\npopup/item_0/text = \"CN\"\npopup/item_0/id = 0\npopup/item_1/text = \"EN\"\npopup/item_1/id = 1\npopup/item_2/text = \"JP\"\npopup/item_2/id = 2\nscript = ExtResource(\"8_f6f5a\")\n\n[node name=\"WelcomeButton\" type=\"Button\" parent=\".\"]\noffset_left = 987.0\noffset_top = 615.0\noffset_right = 1100.0\noffset_bottom = 646.0\ntext = \"Say welcome\"\nscript = ExtResource(\"9_jo4tj\")\nwelcome_option_button_path = NodePath(\"../WelcomeOptionButton\")\n"
  },
  {
    "path": "demo/low_level_2D/FmodTest.gd",
    "content": "extends Node\n\nvar master_string_bank: FmodBank\nvar master_bank: FmodBank\nvar music_bank: FmodBank\nvar vehicles_bank: FmodBank\nvar sfx_bank: FmodBank\n\n# Called when the node enters the scene tree for the first time.\nfunc _enter_tree():\n\t# load banks\n# warning-ignore:return_value_discarded\n\tmaster_string_bank = FmodServer.load_bank(\"res://assets/Banks/Master.strings.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n# warning-ignore:return_value_discarded\n\tmaster_bank = FmodServer.load_bank(\"res://assets/Banks/Master.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n# warning-ignore:return_value_discarded\n\tmusic_bank = FmodServer.load_bank(\"res://assets/Banks/Music.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n# warning-ignore:return_value_discarded\n\tvehicles_bank = FmodServer.load_bank(\"res://assets/Banks/Vehicles.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n# warning-ignore:return_value_discarded\n\tsfx_bank = FmodServer.load_bank(\"res://assets/Banks/SFX.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\tprint(\"Fmod initialised.\")\n"
  },
  {
    "path": "demo/low_level_2D/FmodTest.gd.uid",
    "content": "uid://dn0b4rle1712\n"
  },
  {
    "path": "demo/low_level_2D/LangChooseButton.gd",
    "content": "class_name WelcomeOptionButton extends OptionButton\n\n\nvar lang_bank: FmodBank\n\nfunc _enter_tree():\n\tconnect(\"item_selected\", _on_item_selected)\n\nfunc _on_item_selected(index: int):\n\tif index == -1:\n\t\tlang_bank = null\n\t\treturn\n\tvar bank_path := \"res://assets/Banks/Dialogue_%s.bank\" % get_item_text(index)\n# warning-ignore:return_value_discarded\n\tlang_bank = FmodServer.load_bank(bank_path, FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n"
  },
  {
    "path": "demo/low_level_2D/LangChooseButton.gd.uid",
    "content": "uid://bd3konietc7md\n"
  },
  {
    "path": "demo/low_level_2D/Listener.gd",
    "content": "extends CharacterBody2D\n\nvar distance_traveled := 0\n\n# Called when the node enters the scene tree for the first time.\nfunc _ready():\n\t# register listener\n\tFmodServer.add_listener(0, self)\n\tprint(\"Listener set.\")\n\treturn\n\t\nfunc _process(delta):\n\tvar direction: Vector2 = Vector2(0,0)\n\tvar rotation_dir = 0\n\tif Input.is_action_pressed(\"right\"):\n\t\tdirection.x += 1\n\tif Input.is_action_pressed(\"left\"):\n\t\tdirection.x -= 1\n\tif Input.is_action_pressed(\"up\"):\n\t\tdirection.y -= 1\n\tif Input.is_action_pressed(\"down\"):\n\t\tdirection.y += 1\n\tif Input.is_action_pressed(\"rotate_right\"):\n\t\trotation_dir = 1\n\tif Input.is_action_pressed(\"rotate_left\"):\n\t\trotation_dir = -1\n\tif direction != Vector2(0,0):\n\t\tdistance_traveled += delta * 200\n\t\tif distance_traveled >= 35:\n\t\t\tdistance_traveled = 0\n\t\t\tFmodServer.play_one_shot(\"event:/Character/Player Footsteps\")\n\tdirection = direction.normalized()\n\tdirection.x = direction.x * delta * 200\n\tdirection.y = direction.y * delta * 200\n\n\tself.position += direction\n\tself.rotate(rotation_dir * delta * 5)\n\tif Input.is_action_pressed(\"lock_listener\"):\n\t\tFmodServer.set_listener_lock(0, !FmodServer.get_listener_lock(0))\n\telif Input.is_action_pressed(\"kill\"):\n\t\tself.queue_free()\n"
  },
  {
    "path": "demo/low_level_2D/Listener.gd.uid",
    "content": "uid://c4p1w0xlxjs4l\n"
  },
  {
    "path": "demo/low_level_2D/Listener2.gd",
    "content": "extends Node2D\n\n# Called when the node enters the scene tree for the first time.\nfunc _ready():\n\t# register listener\n\tFmodServer.add_listener(1, self)\n\tprint(\"Listener set.\")\n\treturn\n"
  },
  {
    "path": "demo/low_level_2D/Listener2.gd.uid",
    "content": "uid://luh7ddqnjxdb\n"
  },
  {
    "path": "demo/low_level_2D/WelcomeButton.gd",
    "content": "extends Button\n\n\n@export var welcome_option_button_path: NodePath\nvar welcome_option_button: WelcomeOptionButton\n\nfunc _enter_tree():\n\tconnect(\"pressed\", _on_pressed)\n\nfunc _ready():\n\twelcome_option_button = get_node(welcome_option_button_path)\n\nfunc _on_pressed():\n\tif welcome_option_button.lang_bank == null:\n\t\treturn\n\tvar event_instance = FmodServer.create_event_instance(\"event:/Character/Dialogue\")\n\tevent_instance.set_programmer_callback(\"welcome\")\n\tevent_instance.start()\n"
  },
  {
    "path": "demo/low_level_2D/WelcomeButton.gd.uid",
    "content": "uid://cpd21piypux6c\n"
  },
  {
    "path": "demo/project.godot",
    "content": "; Engine configuration file.\n; It's best edited using the editor UI and not directly,\n; since the parameters that go here are not all obvious.\n;\n; Format:\n;   [section] ; section goes between []\n;   param=value ; assign values to parameters\n\nconfig_version=5\n\n[Fmod]\n\n\"3D Settings/distance_factor\"=64.0\nGeneral/banks_path=\"res://assets/Banks\"\n\n[android]\n\nmodules=\"org/godotengine/godot/FmodSingleton\"\n\n[application]\n\nconfig/name=\"Fmod Demo\"\nrun/main_scene=\"res://low_level_2D/FmodScriptTest.tscn\"\nconfig/features=PackedStringArray(\"4.5\")\nconfig/icon=\"uid://cih2yjmtjoohg\"\n\n[autoload]\n\nFmodManager=\"*res://addons/fmod/FmodManager.gd\"\n\n[dotnet]\n\nproject/assembly_name=\"Fmod Demo\"\n\n[editor_plugins]\n\nenabled=PackedStringArray(\"res://addons/fmod/plugin.cfg\", \"res://addons/gut/plugin.cfg\")\n\n[filesystem]\n\nimport/blender/enabled=false\n\n[input]\n\nui_accept={\n\"deadzone\": 0.5,\n\"events\": []\n}\nui_select={\n\"deadzone\": 0.5,\n\"events\": []\n}\nui_cancel={\n\"deadzone\": 0.5,\n\"events\": []\n}\nui_focus_next={\n\"deadzone\": 0.5,\n\"events\": []\n}\nui_focus_prev={\n\"deadzone\": 0.5,\n\"events\": []\n}\nui_left={\n\"deadzone\": 0.5,\n\"events\": []\n}\nui_right={\n\"deadzone\": 0.5,\n\"events\": []\n}\nui_up={\n\"deadzone\": 0.5,\n\"events\": []\n}\nui_down={\n\"deadzone\": 0.5,\n\"events\": []\n}\nui_page_up={\n\"deadzone\": 0.5,\n\"events\": []\n}\nui_page_down={\n\"deadzone\": 0.5,\n\"events\": []\n}\nui_home={\n\"deadzone\": 0.5,\n\"events\": []\n}\nui_end={\n\"deadzone\": 0.5,\n\"events\": []\n}\nright={\n\"deadzone\": 0.5,\n\"events\": [Object(InputEventKey,\"resource_local_to_scene\":false,\"resource_name\":\"\",\"device\":0,\"window_id\":0,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"pressed\":false,\"keycode\":0,\"physical_keycode\":68,\"key_label\":0,\"unicode\":0,\"location\":0,\"echo\":false,\"script\":null)\n]\n}\nleft={\n\"deadzone\": 0.5,\n\"events\": [Object(InputEventKey,\"resource_local_to_scene\":false,\"resource_name\":\"\",\"device\":0,\"window_id\":0,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"pressed\":false,\"keycode\":0,\"physical_keycode\":65,\"key_label\":0,\"unicode\":0,\"location\":0,\"echo\":false,\"script\":null)\n]\n}\ndown={\n\"deadzone\": 0.5,\n\"events\": [Object(InputEventKey,\"resource_local_to_scene\":false,\"resource_name\":\"\",\"device\":0,\"window_id\":0,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"pressed\":false,\"keycode\":0,\"physical_keycode\":83,\"key_label\":0,\"unicode\":0,\"location\":0,\"echo\":false,\"script\":null)\n]\n}\nspace={\n\"deadzone\": 0.5,\n\"events\": [null, Object(InputEventKey,\"resource_local_to_scene\":false,\"resource_name\":\"\",\"device\":0,\"window_id\":0,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"pressed\":false,\"keycode\":0,\"physical_keycode\":32,\"key_label\":0,\"unicode\":0,\"location\":0,\"echo\":false,\"script\":null)\n]\n}\nkill={\n\"deadzone\": 0.5,\n\"events\": [Object(InputEventKey,\"resource_local_to_scene\":false,\"resource_name\":\"\",\"device\":0,\"window_id\":0,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"pressed\":false,\"keycode\":0,\"physical_keycode\":75,\"key_label\":0,\"unicode\":0,\"location\":0,\"echo\":false,\"script\":null)\n]\n}\nlock_listener={\n\"deadzone\": 0.5,\n\"events\": [Object(InputEventKey,\"resource_local_to_scene\":false,\"resource_name\":\"\",\"device\":0,\"window_id\":0,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"pressed\":false,\"keycode\":0,\"physical_keycode\":0,\"key_label\":0,\"unicode\":0,\"location\":0,\"echo\":false,\"script\":null)\n]\n}\nrotate_left={\n\"deadzone\": 0.5,\n\"events\": [Object(InputEventKey,\"resource_local_to_scene\":false,\"resource_name\":\"\",\"device\":-1,\"window_id\":0,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"pressed\":false,\"keycode\":0,\"physical_keycode\":4194319,\"key_label\":0,\"unicode\":0,\"location\":0,\"echo\":false,\"script\":null)\n]\n}\nrotate_right={\n\"deadzone\": 0.5,\n\"events\": [Object(InputEventKey,\"resource_local_to_scene\":false,\"resource_name\":\"\",\"device\":-1,\"window_id\":0,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"pressed\":false,\"keycode\":0,\"physical_keycode\":4194321,\"key_label\":0,\"unicode\":0,\"location\":0,\"echo\":false,\"script\":null)\n]\n}\nkill_event={\n\"deadzone\": 0.5,\n\"events\": [Object(InputEventKey,\"resource_local_to_scene\":false,\"resource_name\":\"\",\"device\":0,\"window_id\":0,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"pressed\":false,\"keycode\":0,\"physical_keycode\":0,\"key_label\":0,\"unicode\":0,\"location\":0,\"echo\":false,\"script\":null)\n]\n}\nup={\n\"deadzone\": 0.5,\n\"events\": [Object(InputEventKey,\"resource_local_to_scene\":false,\"resource_name\":\"\",\"device\":0,\"window_id\":0,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"pressed\":false,\"keycode\":0,\"physical_keycode\":87,\"key_label\":0,\"unicode\":0,\"location\":0,\"echo\":false,\"script\":null)\n]\n}\nengine_power_up={\n\"deadzone\": 0.5,\n\"events\": [Object(InputEventKey,\"resource_local_to_scene\":false,\"resource_name\":\"\",\"device\":-1,\"window_id\":0,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"pressed\":false,\"keycode\":0,\"physical_keycode\":4194320,\"key_label\":0,\"unicode\":0,\"location\":0,\"echo\":false,\"script\":null)\n]\n}\nengine_power_down={\n\"deadzone\": 0.5,\n\"events\": [Object(InputEventKey,\"resource_local_to_scene\":false,\"resource_name\":\"\",\"device\":-1,\"window_id\":0,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"pressed\":false,\"keycode\":0,\"physical_keycode\":4194322,\"key_label\":0,\"unicode\":0,\"location\":0,\"echo\":false,\"script\":null)\n]\n}\n\n[physics]\n\n3d/default_gravity=98.0\n\n[rendering]\n\ntextures/vram_compression/import_etc2_astc=true\n"
  },
  {
    "path": "demo/run_tests.sh",
    "content": "if [ -z \"$1\" ]; then\n  echo \"ERROR: Please provide the path to the Godot executable as the first argument.\"\n  exit 1\nfi\n\nGODOT_BIN=\"$1\"\nPROJECT_PATH=\"$PWD\"\nDELAY_SEC=5\nMAX_ATTEMPTS=3\n\nretry_import() {\n  for (( attempt=1; attempt<=MAX_ATTEMPTS; attempt++ )); do\n    echo \"INFO: Attempt $attempt/$MAX_ATTEMPTS: waiting $DELAY_SEC before import…\"\n    sleep \"$DELAY_SEC\"\n\n    if \"$GODOT_BIN\" --headless --path \"$PROJECT_PATH\" --import; then\n      echo \"INFO: Import succeeded on attempt $attempt.\"\n      return 0\n    else\n      echo \"WARN: Import failed on attempt $attempt. Retrying…\"\n    fi\n  done\n  return 1\n}\n\n\n# Pre-import the project (headless). Try --import first, then fall back to headless editor.\nif ! retry_import; then\n  echo \"INFO: All --import attempts failed. Falling back to headless editor import…\"\n  sleep \"$DELAY_SEC\"\n  if ! \"$GODOT_BIN\" --headless --path \"$PROJECT_PATH\" --editor --quit; then\n    echo \"WARNING: Fallback import (--editor --quit) failed. Continuing anyway…\"\n  fi\nfi\n\n\n\"$GODOT_BIN\" -s --headless --path \"$PROJECT_PATH\" addons/gut/gut_cmdln.gd | (\n    tests=0\n    passing=0\n\n    while read -r line\n    do\n        echo \"$line\"\n\n        # Match line that starts with \"Tests\"\n        if echo \"$line\" | grep -q \"^Tests\"; then\n            tests=$(echo \"$line\" | awk '{print $NF}')\n        fi\n\n        # Match line that starts with \"Passing Tests\"\n        if echo \"$line\" | grep -q \"^Passing Tests\"; then\n            passing=$(echo \"$line\" | awk '{print $NF}')\n        fi\n    done\n\n    echo \"Retrieved values: tests=$tests passing=$passing\"\n\n    if [[ \"$tests\" -eq 0 ]]; then\n       echo \"ERROR: No tests were found.\"\n       exit 1\n    fi\n\n    if [[ \"$tests\" -eq \"$passing\" ]]; then\n        exit 0\n    else\n        echo \"ERROR: Some assertions failed!\"\n        exit 1\n    fi\n)\n\nresult=$?\n\n# Exit with the result of the subshell\nexit $result\n"
  },
  {
    "path": "demo/test/integration/init",
    "content": ""
  },
  {
    "path": "demo/test/tests.tscn",
    "content": "[gd_scene format=3 uid=\"uid://cmtd8wjw7vfon\"]\n\n[node name=\"Control\" type=\"Control\"]\nlayout_mode = 3\nanchors_preset = 15\nanchor_right = 1.0\nanchor_bottom = 1.0\ngrow_horizontal = 2\ngrow_vertical = 2\n"
  },
  {
    "path": "demo/test/unit/test_bank.gd",
    "content": "extends \"res://addons/gut/test.gd\"\n\nclass TestBank:\n\textends \"res://addons/gut/test.gd\"\n\t\n\tvar sprite: Sprite2D = Sprite2D.new()\n\t\n\tvar master_strings_bank: FmodBank\n\tvar masterBank: FmodBank\n\tvar musicBank: FmodBank\n\tvar vehicleBank: FmodBank\n\t\n\tfunc before_all():\n\t\t# load banks\n\t\t# warning-ignore:return_value_discarded\n\t\tmaster_strings_bank = FmodServer.load_bank(\"res://assets/Banks/Master.strings.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t# warning-ignore:return_value_discarded\n\t\tmasterBank = FmodServer.load_bank(\"res://assets/Banks/Master.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t# warning-ignore:return_value_discarded\n\t\tmusicBank = FmodServer.load_bank(\"res://assets/Banks/Music.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t# warning-ignore:return_value_discarded\n\t\tvehicleBank = FmodServer.load_bank(\"res://assets/Banks/Vehicles.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\tvar desired_value = FmodServer.FMOD_STUDIO_LOADING_STATE_LOADED\n\t\tassert_eq(vehicleBank.get_loading_state(), desired_value, \"Loading state should be FMOD_STUDIO_LOADING_STATE_LOADED\")\n\t\tFmodServer.set_listener_number(1)\n\t\tget_tree().get_root().add_child(sprite)\n\t\tFmodServer.add_listener(0, sprite)\n\t\n\tfunc after_all():\n\t\tFmodServer.remove_listener(0, sprite)\n\t\n\tfunc test_assert_bank_bus_count():\n\t\tvar desiredValue: int = 0\n\t\tassert_eq(musicBank.get_bus_count(), desiredValue, \"Music bank should have \" + str(desiredValue) + \" buses\")\n\t\tdesiredValue = 12\n\t\tassert_eq(masterBank.get_bus_count(), desiredValue, \"Master bank should have \" + str(desiredValue) + \" buses\")\n\t\tdesiredValue = 0\n\t\tassert_eq(vehicleBank.get_bus_count(), desiredValue, \"Vehicles bank should have \" + str(desiredValue) + \" buses\")\n\t\n\tfunc test_assert_bank_event_count():\n\t\tvar desiredValue: int = 4\n\t\tassert_eq(musicBank.get_event_description_count(), desiredValue, \"Music bank should have \" + str(desiredValue) + \" events\")\n\t\tdesiredValue = 5\n\t\tassert_eq(masterBank.get_event_description_count(), desiredValue, \"Master bank should have \" + str(desiredValue) + \" events\")\n\t\tdesiredValue = 2\n\t\tassert_eq(vehicleBank.get_event_description_count(), desiredValue, \"Vehicles bank should have \" + str(desiredValue) + \" events\")\n\t\n\tfunc test_assert_bank_string_count():\n\t\tvar desiredValue: int = 0\n\t\tassert_eq(musicBank.get_string_count(), desiredValue, \"Music bank should have \" + str(desiredValue) + \" strings\")\n\t\tassert_eq(masterBank.get_string_count(), desiredValue, \"Master bank should have \" + str(desiredValue) + \" strings\")\n\t\tassert_eq(vehicleBank.get_string_count(), desiredValue, \"Vehicles bank should have \" + str(desiredValue) + \" strings\")\n\t\n\tfunc test_assert_bank_vca_count():\n\t\tvar desiredValue: int = 0\n\t\tassert_eq(musicBank.get_VCA_count(), desiredValue, \"Music bank should have \" + str(desiredValue) + \" VCAs\")\n\t\tdesiredValue = 3\n\t\tassert_eq(masterBank.get_VCA_count(), desiredValue, \"Master bank should have \" + str(desiredValue) + \" VCAs\")\n\t\tdesiredValue = 0\n\t\tassert_eq(vehicleBank.get_VCA_count(), desiredValue, \"Vehicles bank should have \" + str(desiredValue) + \" VCAs\")\n"
  },
  {
    "path": "demo/test/unit/test_bank.gd.uid",
    "content": "uid://bttd8qhpwy7c5\n"
  },
  {
    "path": "demo/test/unit/test_bus.gd",
    "content": "extends \"res://addons/gut/test.gd\"\n\nclass TestBus:\n\textends \"res://addons/gut/test.gd\"\n\t\n\tvar fmodEvent: FmodEvent\n\tvar sprite: Sprite2D = Sprite2D.new()\n\t\n\tvar banks := Array()\n\t\n\tfunc before_all():\n\t\t# load banks\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Master.strings.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Master.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Music.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Vehicles.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\tFmodServer.set_listener_number(1)\n\t\tfmodEvent = FmodServer.create_event_instance(\"event:/Vehicles/Car Engine\")\n\t\tget_tree().get_root().add_child(sprite)\n\t\tFmodServer.add_listener(0, sprite)\n\t\n\tfunc after_all():\n\t\tfmodEvent.release()\n\t\tFmodServer.remove_listener(0, sprite)\n\t\n\tfunc test_assert_should_has_master_bus():\n\t\tvar wanted: String = \"bus:/\"\n\t\tassert_true(FmodServer.check_bus_path(wanted), wanted + \" should be present\")\n\t\n\tfunc test_assert_should_has_master_bus_using_guid():\n\t\tvar wanted = \"{af9d027a-3a1f-49a8-a9ef-4cbe20673632}\"\n\t\tassert_true(FmodServer.check_bus_guid(wanted), wanted + \" should be present\")\n\t\n\tfunc test_assert_should_not_has_bus():\n\t\tvar wanted: String = \"undefined\"\n\t\tassert_false(FmodServer.check_bus_path(wanted), wanted + \" should not be present\")\n\t\n\tfunc test_assert_should_not_has_bus_using_guid():\n\t\tvar wanted = \"{29583fe9-eee9-4c67-94e1-57d5f6c552af}\"\n\t\tassert_false(FmodServer.check_bus_guid(wanted), wanted + \" should not be present\")\n\t\n\tfunc test_assert_mute_unmute():\n\t\tvar masterBus: FmodBus = FmodServer.get_bus(\"bus:/\")\n\t\tassert_false(masterBus.mute, \"Master bus should not be muted\")\n\t\tmasterBus.mute = true\n\t\tassert_true(masterBus.mute, \"Master bus should be muted\")\n\t\tmasterBus.mute = false\n\t\tassert_false(masterBus.mute, \"Master bus should not be muted\")\n\t\n\tfunc test_assert_pause_unpause():\n\t\tvar masterBus: FmodBus = FmodServer.get_bus(\"bus:/\")\n\t\tassert_false(masterBus.paused, \"Master bus should not be paused\")\n\t\tmasterBus.paused = true\n\t\tassert_true(masterBus.paused, \"Master bus should be paused\")\n\t\tmasterBus.paused = false\n\t\tassert_false(masterBus.paused, \"Master bus should not be paused\")\n\t\n\tfunc test_assert_volume():\n\t\t_test_assert_volume(false)\n\t\t_test_assert_volume(true)\n\t\n\tfunc _test_assert_volume(is_guid: bool):\n\t\tvar masterBus: FmodBus = FmodServer.get_bus_from_guid(\"{af9d027a-3a1f-49a8-a9ef-4cbe20673632}\") if is_guid else FmodServer.get_bus(\"bus:/\")\n\t\t\n\t\tvar desiredValue: float = 1.0\n\t\tassert_eq(masterBus.volume, desiredValue, \"Bus volume should be \" + str(desiredValue))\n\t\tdesiredValue = 0.5\n\t\tmasterBus.volume = desiredValue\n\t\tassert_eq(masterBus.volume, desiredValue, \"Bus volume should be \" + str(desiredValue))\n\t\tmasterBus.volume = 1\n\t\n\tfunc test_assert_bus_stop_events():\n\t\tvar fmodEvent2: FmodEvent = FmodServer.create_event_instance(\"event:/Vehicles/Car Engine\")\n\t\tfmodEvent.start()\n\t\tfmodEvent2.start()\n\t\tawait wait_seconds(2)\n\t\tassert_eq(fmodEvent.get_playback_state(), FmodServer.FMOD_STUDIO_PLAYBACK_PLAYING, \"Event \" + str(fmodEvent) + \" playback state should be \" + str(FmodServer.FMOD_STUDIO_PLAYBACK_PLAYING))\n\t\tassert_eq(fmodEvent2.get_playback_state(), FmodServer.FMOD_STUDIO_PLAYBACK_PLAYING, \"Event \" + str(fmodEvent2) + \" playback state should be \" + str(FmodServer.FMOD_STUDIO_PLAYBACK_PLAYING))\n\t\tFmodServer.get_bus(\"bus:/\").stop_all_events(FmodServer.FMOD_STUDIO_STOP_IMMEDIATE)\n\t\tawait wait_seconds(2)\n\t\tassert_eq(fmodEvent.get_playback_state(), FmodServer.FMOD_STUDIO_PLAYBACK_STOPPED, \"Event \" + str(fmodEvent) + \" playback state should be \" + str(FmodServer.FMOD_STUDIO_PLAYBACK_STOPPED))\n\t\tassert_eq(fmodEvent2.get_playback_state(), FmodServer.FMOD_STUDIO_PLAYBACK_STOPPED, \"Event \" + str(fmodEvent2) + \" playback state should be \" + str(FmodServer.FMOD_STUDIO_PLAYBACK_STOPPED))\n\t\tfmodEvent2.release()\n"
  },
  {
    "path": "demo/test/unit/test_bus.gd.uid",
    "content": "uid://dbh20bblqx6ah\n"
  },
  {
    "path": "demo/test/unit/test_callbacks.gd",
    "content": "extends \"res://addons/gut/test.gd\"\n\nvar sprite: Sprite2D = Sprite2D.new()\n\nvar banks := Array()\n\nfunc before_all():\n\t# load banks\n\t# warning-ignore:return_value_discarded\n\tbanks.append(\n\t\tFmodServer.load_bank(\"res://assets/Banks/Master.strings.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t)\n\t# warning-ignore:return_value_discarded\n\tbanks.append(\n\t\tFmodServer.load_bank(\"res://assets/Banks/Master.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t)\n\t# warning-ignore:return_value_discarded\n\tbanks.append(\n\t\tFmodServer.load_bank(\"res://assets/Banks/Music.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t)\n\t# warning-ignore:return_value_discarded\n\tbanks.append(\n\t\tFmodServer.load_bank(\"res://assets/Banks/Vehicles.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t)\n\tFmodServer.set_listener_number(1)\n\tget_tree().get_root().add_child(sprite)\n\tFmodServer.add_listener(0, sprite)\n\nfunc after_all():\n\tFmodServer.remove_listener(0, sprite)\n\nfunc test_assert_has_signals():\n\tvar emitter: FmodEventEmitter2D = FmodEventEmitter2D.new()\n\tassert_has_signal(emitter, \"timeline_beat\")\n\tassert_has_signal(emitter, \"timeline_marker\")\n\tassert_has_signal(emitter, \"start_failed\")\n\tassert_has_signal(emitter, \"started\")\n\tassert_has_signal(emitter, \"restarted\")\n\tassert_has_signal(emitter, \"stopped\")\n\temitter.free()\n\nvar callback_called = false\n\nfunc on_callback(_dict: Dictionary, type: int):\n\tcallback_called = true\n\nfunc test_assert_set_callback():\n\twatch_signals(FmodServer)\n\tvar fmod_event: FmodEvent = FmodServer.create_event_instance(\"event:/Music/Level 02\")\n\tfmod_event.volume = 0\n\tfmod_event.start()\n\tfmod_event.set_callback(on_callback, FmodServer.FMOD_STUDIO_EVENT_CALLBACK_ALL)\n\tawait wait_seconds(2)\n\tassert_true(callback_called)\n\tfmod_event.stop(FmodServer.FMOD_STUDIO_STOP_IMMEDIATE)\n\tfmod_event.release()\n"
  },
  {
    "path": "demo/test/unit/test_callbacks.gd.uid",
    "content": "uid://dcq4k3m170y4f\n"
  },
  {
    "path": "demo/test/unit/test_desc_event.gd",
    "content": "extends \"res://addons/gut/test.gd\"\n\nclass TestEventDescription:\n\textends \"res://addons/gut/test.gd\"\n\t\n\tvar fmodEvent: FmodEvent\n\tvar sprite: Sprite2D = Sprite2D.new()\n\t\n\tvar banks := Array()\n\t\n\tfunc before_all():\n\t\t# load banks\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Master.strings.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Master.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Music.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Vehicles.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\tFmodServer.set_listener_number(1)\n\t\tget_tree().get_root().add_child(sprite)\n\t\tFmodServer.add_listener(0, sprite)\n\t\tfmodEvent = FmodServer.create_event_instance(\"event:/Vehicles/Car Engine\")\n\t\n\tfunc after_all():\n\t\tfmodEvent.release()\n\t\tFmodServer.remove_listener(0, sprite)\n\t\n\tfunc test_assert_should_create_and_release():\n\t\tvar desired_value: int = 2\n\t\tvar fmodEvent2: FmodEvent = FmodServer.create_event_instance(\"event:/Vehicles/Car Engine\")\n\t\tvar instance_list: Array = FmodServer.get_event(\"event:/Vehicles/Car Engine\").get_instance_list()\n\t\tassert_eq(instance_list.size(), desired_value, \"Event description list size should be \" + str(desired_value))\n\t\tfmodEvent2.release()\n\t\tdesired_value = 1\n\t\tawait wait_seconds(2)\n\t\tassert_eq(FmodServer.get_event(\"event:/Vehicles/Car Engine\").get_instance_list().size(), desired_value, \"Event description list size should be \" + str(desired_value))\n\t\tfmodEvent2 = FmodServer.create_event_instance(\"event:/Vehicles/Car Engine\")\n\t\tvar fmodEvent3: FmodEvent = FmodServer.create_event_instance(\"event:/Vehicles/Car Engine\")\n\t\tdesired_value = 3\n\t\tassert_eq(FmodServer.get_event(\"event:/Vehicles/Car Engine\").get_instance_list().size(), desired_value, \"Event description list size should be \" + str(desired_value))\n\t\tFmodServer.get_event(\"event:/Vehicles/Car Engine\").release_all_instances()\n\t\tdesired_value = 0\n\t\tawait wait_seconds(2)\n\t\tassert_eq(FmodServer.get_event(\"event:/Vehicles/Car Engine\").get_instance_count(), desired_value, \"Event description list size should be \" + str(desired_value))\n\t\tfmodEvent = FmodServer.create_event_instance(\"event:/Vehicles/Car Engine\")\n\t\n\tfunc test_assert_should_create_and_release_using_guid():\n\t\tvar desired_value: int = 2\n\t\tvar fmodEvent2: FmodEvent = FmodServer.create_event_instance_with_guid(\"{0c8363b4-23af-4f9c-af4b-0951bfd37d84}\")\n\t\tvar instance_list: Array = FmodServer.get_event_from_guid(\"{0c8363b4-23af-4f9c-af4b-0951bfd37d84}\").get_instance_list()\n\t\tassert_eq(instance_list.size(), desired_value, \"Event description list size should be \" + str(desired_value))\n\t\tfmodEvent2.release()\n\t\tdesired_value = 1\n\t\tawait wait_seconds(2)\n\t\tassert_eq(FmodServer.get_event(\"event:/Vehicles/Car Engine\").get_instance_list().size(), desired_value, \"Event description list size should be \" + str(desired_value))\n\t\tfmodEvent2 = FmodServer.create_event_instance_with_guid(\"{0c8363b4-23af-4f9c-af4b-0951bfd37d84}\")\n\t\tvar fmodEvent3: FmodEvent = FmodServer.create_event_instance(\"event:/Vehicles/Car Engine\")\n\t\tdesired_value = 3\n\t\tassert_eq(FmodServer.get_event_from_guid(\"{0c8363b4-23af-4f9c-af4b-0951bfd37d84}\").get_instance_list().size(), desired_value, \"Event description list size should be \" + str(desired_value))\n\t\tFmodServer.get_event_from_guid(\"{0c8363b4-23af-4f9c-af4b-0951bfd37d84}\").release_all_instances()\n\t\tdesired_value = 0\n\t\tawait wait_seconds(2)\n\t\tassert_eq(FmodServer.get_event_from_guid(\"{0c8363b4-23af-4f9c-af4b-0951bfd37d84}\").get_instance_count(), desired_value, \"Event description list size should be \" + str(desired_value))\n\t\tfmodEvent = FmodServer.create_event_instance(\"event:/Vehicles/Car Engine\")\n\t\n\tfunc test_assert_should_be_3d():\n\t\t_test_assert_should_be_3d(false)\n\t\t_test_assert_should_be_3d(true)\n\t\n\tfunc _test_assert_should_be_3d(is_guid: bool):\n\t\tvar event: FmodEventDescription = FmodServer.get_event_from_guid(\"{0c8363b4-23af-4f9c-af4b-0951bfd37d84}\") if is_guid else FmodServer.get_event(\"event:/Vehicles/Car Engine\")\n\t\tassert_true(event.is_3d(), \"Event description should be 3D\")\n\t\n\tfunc test_assert_should_not_be_oneshot():\n\t\tassert_false(FmodServer.get_event(\"event:/Vehicles/Car Engine\").is_one_shot(), \"Event description should not be oneshot\")\n\t\n\tfunc test_assert_should_not_be_snapshot():\n\t\tassert_false(FmodServer.get_event(\"event:/Vehicles/Car Engine\").is_snapshot(), \"Event description should not be snapshot\")\n\t\n\tfunc test_assert_should_not_be_stream():\n\t\tassert_false(FmodServer.get_event(\"event:/Vehicles/Car Engine\").is_stream(), \"Event description should not be stream\")\n\t\n\tfunc test_assert_should_not_have_cue():\n\t\tassert_false(FmodServer.get_event(\"event:/Vehicles/Car Engine\").has_sustain_point(), \"Event description should not have cue\")\n\t\n\tfunc test_assert_min_max_distance():\n\t\tvar desiredMin: float = 1.0\n\t\tvar desiredMax: float = 20.0\n\t\tvar minMaxDistance = FmodServer.get_event(\"event:/Vehicles/Car Engine\").get_min_max_distance()\n\t\tassert_eq(minMaxDistance[0], desiredMin, \"Event description minimum distance should be \" + str(desiredMin))\n\t\tassert_eq(minMaxDistance[1], desiredMax, \"Event description maximum distance should be \" + str(desiredMax))\n\t\n\tfunc test_assert_sound_size():\n\t\tvar desiredValue: float = 2.0\n\t\tassert_eq(FmodServer.get_event(\"event:/Vehicles/Car Engine\").get_sound_size(), desiredValue, \"Event description sound size should be \" + str(desiredValue))\n\t\t\n\tfunc test_assert_should_retrieve_user_property_by_name():\n\t\tvar desiredSize: int = 0\n\t\tvar property = FmodServer.get_event(\"event:/Vehicles/Car Engine\").get_user_property(\"abc\")\n\t\tassert_eq(property.size(), desiredSize, \"Number of user properties should be \" + str(desiredSize))\n\t\t\n\tfunc test_assert_should_retrieve_user_property_by_index():\n\t\tvar desiredSize: int = 0\n\t\tvar property = FmodServer.get_event(\"event:/Vehicles/Car Engine\").user_property_by_index(0)\n\t\tassert_eq(property.size(), desiredSize, \"Number of user properties should be \" + str(desiredSize))\n\n\tfunc test_assert_should_retrieve_user_property_count():\n\t\tvar desiredSize: int = 0\n\t\tvar property = FmodServer.get_event(\"event:/Vehicles/Car Engine\").get_user_property_count()\n\t\tassert_eq(property, desiredSize, \"Number of user properties should be \" + str(desiredSize))\n"
  },
  {
    "path": "demo/test/unit/test_desc_event.gd.uid",
    "content": "uid://jqjegp14uulq\n"
  },
  {
    "path": "demo/test/unit/test_event.gd",
    "content": "extends \"res://addons/gut/test.gd\"\n\nclass TestEvent:\n\textends \"res://addons/gut/test.gd\"\n\t\n\tvar fmodEvent: FmodEvent\n\tvar sprite: Sprite2D = Sprite2D.new()\n\t\n\tvar banks := Array()\n\t\n\tfunc before_all():\n\t\t# load banks\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Master.strings.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Master.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Music.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Vehicles.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\tFmodServer.set_listener_number(1)\n\t\tget_tree().get_root().add_child(sprite)\n\t\tFmodServer.add_listener(0, sprite)\n\t\tfmodEvent = FmodServer.create_event_instance(\"event:/Vehicles/Car Engine\")\n\t\tfmodEvent.set_parameter_by_name(\"RPM\", 600)\n\t\tfmodEvent.start()\n\t\n\tfunc after_all():\n\t\tfmodEvent.stop(FmodServer.FMOD_STUDIO_STOP_IMMEDIATE)\n\t\tfmodEvent.release()\n\t\tFmodServer.remove_listener(0, sprite)\n\t\n\tfunc test_should_has_event():\n\t\tvar wanted: String = \"event:/Vehicles/Car Engine\"\n\t\tassert_true(FmodServer.check_event_path(wanted), wanted + \" should be present\")\n\t\n\tfunc test_should_has_event_guid():\n\t\tvar wanted: String = \"{0c8363b4-23af-4f9c-af4b-0951bfd37d84}\"\n\t\tassert_true(FmodServer.check_event_guid(wanted), wanted + \" should be present\")\n\t\n\tfunc test_should_not_has_event():\n\t\tvar wanted: String = \"undefined\"\n\t\tassert_false(FmodServer.check_event_path(wanted), wanted + \" should not be present\")\n\t\n\tfunc test_should_not_has_event_guid():\n\t\tvar wanted: String = \"{29583fe9-eee9-4c67-94e1-57d5f6c552af}\"\n\t\tassert_false(FmodServer.check_event_guid(wanted), wanted + \" should not be present\")\n\t\n\tfunc test_assert_set_volume():\n\t\tvar desired_value: float = 4.0\n\t\tfmodEvent.volume = desired_value\n\t\tassert_eq(fmodEvent.volume, desired_value, \"Event volume should be 4\")\n\t\n\tfunc test_assert_set_pitch():\n\t\tvar desired_value: float = 0.75\n\t\tfmodEvent.pitch = desired_value\n\t\tassert_eq(fmodEvent.pitch, desired_value, \"Event pitch should be 0.75\")\n\t\n\tfunc test_assert_paused():\n\t\tfmodEvent.paused = true\n\t\tassert_true(fmodEvent.paused, \"Event should be paused\")\n\t\n\tfunc test_assert_timeline_position():\n\t\tvar desired_value: int = 10\n\t\tfmodEvent.paused = true\n\t\tfmodEvent.position = desired_value\n\t\tawait wait_seconds(2)\n\t\tassert_eq(fmodEvent.position, desired_value, \"Event timeline should be at \" + str(desired_value))\n\t\n\tfunc test_assert_event_reverb():\n\t\tvar desired_value: float = 1.5\n\t\tfmodEvent.set_reverb_level(0, desired_value)\n\t\tassert_eq(fmodEvent.get_reverb_level(0), desired_value, \"Event reverb level should be \" + str(desired_value))\n\t\n\tfunc test_assert_event_parameter_by_name():\n\t\tvar desired_value: float = 600.0\n\t\tassert_eq(fmodEvent.get_parameter_by_name(\"RPM\"), desired_value, \"Event parameter RPM should be \" + str(desired_value))\n\t\n\tfunc test_assert_should_pause_all():\n\t\tfmodEvent.paused = false\n\t\tvar fmodEvent2 = FmodServer.create_event_instance_with_guid(\"{0c8363b4-23af-4f9c-af4b-0951bfd37d84}\")\n\t\tfmodEvent2.start()\n\t\tFmodServer.pause_all_events()\n\t\tassert_true(fmodEvent.paused, \"Event \" + str(fmodEvent) + \" should be paused\")\n\t\tassert_true(fmodEvent2.paused, \"Event \" + str(fmodEvent2) + \" should be paused\")\n\t\tfmodEvent2.stop(FmodServer.FMOD_STUDIO_STOP_IMMEDIATE)\n\t\tfmodEvent2.release()\n\t\n\tfunc test_assert_should_mute_unmute_all():\n\t\tFmodServer.mute_all_events()\n\t\t\n\t\tvar bus = FmodServer.get_bus(\"bus:/\")\n\t\t\n\t\tassert_true(bus.mute, \"Master bus should be muted\")\n\t\tFmodServer.unmute_all_events()\n\t\tassert_false(bus.mute, \"Master bus should not be muted\")\n"
  },
  {
    "path": "demo/test/unit/test_event.gd.uid",
    "content": "uid://mh5ug8qwcjhx\n"
  },
  {
    "path": "demo/test/unit/test_global.gd",
    "content": "extends \"res://addons/gut/test.gd\"\n\nclass TestGlobal:\n\textends \"res://addons/gut/test.gd\"\n\t\n\tvar sprite: Sprite2D = Sprite2D.new()\n\t\n\tvar banks := Array()\n\t\n\tfunc before_all():\n\t\t# load banks\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Master.strings.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Master.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Music.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Vehicles.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\tFmodServer.set_listener_number(1)\n\t\tget_tree().get_root().add_child(sprite)\n\t\tFmodServer.add_listener(0, sprite)\n\t\n\tfunc after_all():\n\t\tFmodServer.remove_listener(0, sprite)\n\t\n\tfunc test_assert_should_have_performance_data():\n\t\tvar perf_data: FmodPerformanceData = FmodServer.get_performance_data()\n\t\tassert_not_null(perf_data, \"Performance data should not be null.\")\n\t\t\n\tfunc test_assert_should_have_dsp_buffer_length():\n\t\tvar buffer_length = FmodServer.get_system_dsp_buffer_length()\n\t\tassert_eq(buffer_length, 512)\n\t\t\n\tfunc test_assert_should_have_dsp_num_buffers():\n\t\tvar num_buffers = FmodServer.get_system_dsp_num_buffers()\n\t\tassert_eq(num_buffers, 4)\n\n\tfunc test_assert_should_have_dsp_buffer_size():\n\t\tvar buffer_size: FmodDspSettings = FmodServer.get_system_dsp_buffer_settings()\n\t\tassert_eq(buffer_size.dsp_buffer_size, 512)\n\t\tassert_eq(buffer_size.dsp_buffer_count, 4)\n"
  },
  {
    "path": "demo/test/unit/test_global.gd.uid",
    "content": "uid://1vqim544mnbm\n"
  },
  {
    "path": "demo/test/unit/test_listener.gd",
    "content": "extends \"res://addons/gut/test.gd\"\n\nclass TestListener:\n\textends \"res://addons/gut/test.gd\"\n\t\n\tvar sprite: Sprite2D = Sprite2D.new()\n\t\n\tvar banks := Array()\n\t\n\tfunc before_all():\n\t\t# load banks\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Master.strings.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Master.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Music.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Vehicles.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\tFmodServer.set_listener_number(1)\n\t\tget_tree().get_root().add_child(sprite)\n\t\tFmodServer.add_listener(0, sprite)\n\t\n\tfunc after_all():\n\t\tFmodServer.remove_listener(0, sprite)\n\t\n\tfunc test_assert_should_set_listener_num():\n\t\tvar desiredValue: int = 1\n\t\tassert_listener_num(desiredValue)\n\t\tFmodServer.set_listener_number(2)\n\t\tdesiredValue = 2\n\t\tassert_listener_num(desiredValue)\n\t\tdesiredValue = 1\n\t\tFmodServer.set_listener_number(1)\n\t\tassert_listener_num(desiredValue)\n\t\n\tfunc test_assert_should_have_proper_weight():\n\t\tvar desiredValue: float = 1.0\n\t\tassert_listener_weight(0, desiredValue)\n\t\tdesiredValue = 2.0\n\t\tFmodServer.set_listener_weight(0, desiredValue)\n\t\tassert_listener_weight(0, desiredValue)\n\t\tdesiredValue = 1.0\n\t\tFmodServer.set_listener_weight(0, desiredValue)\n\t\tFmodServer.set_listener_number(2)\n\t\tdesiredValue = 2.0\n\t\tFmodServer.add_listener(1, sprite)\n\t\tFmodServer.set_listener_weight(1, desiredValue)\n\t\tassert_listener_weight(1, desiredValue)\n\t\tFmodServer.remove_listener(1, sprite)\n\t\tFmodServer.set_listener_number(1)\n\t\n\tfunc test_assert_attach_object_to_listener():\n\t\tvar desired_listener: int = 0;\n\t\tvar node_instance: Object = FmodServer.get_object_attached_to_listener(desired_listener)\n\t\tassert_false(node_instance == null, \"Listener \" + str(desired_listener) + \" should have an object attached\")\n\t\tFmodServer.set_listener_number(2);\n\t\tdesired_listener = 1;\n\t\tassert_no_object_attached_to_listener(desired_listener)\n\t\tFmodServer.add_listener(desired_listener, sprite)\n\t\tassert_true(FmodServer.get_object_attached_to_listener(desired_listener) == node_instance, \"Both listeners should be attached to same object\")\n\t\tFmodServer.remove_listener(1, sprite)\n\t\tassert_no_object_attached_to_listener(desired_listener)\n\t\tFmodServer.set_listener_number(1)\n\t\n\tfunc test_attach_two_object_to_listeners():\n\t\tvar desired_listener := 1\n\t\tFmodServer.set_listener_number(2);\n\t\tFmodServer.add_listener(desired_listener, sprite)\n\t\t\n\t\tassert_eq(FmodServer.get_object_attached_to_listener(desired_listener), sprite)\n\t\t\n\t\tvar node := Node2D.new()\n\t\t\n\t\tFmodServer.add_listener(desired_listener, node)\n\t\tassert_eq(FmodServer.get_object_attached_to_listener(desired_listener), node)\n\t\tFmodServer.remove_listener(desired_listener, sprite)\n\t\tassert_eq(FmodServer.get_object_attached_to_listener(desired_listener), node)\n\t\tFmodServer.remove_listener(desired_listener, node)\n\t\tassert_no_object_attached_to_listener(desired_listener)\n\t\t\n\t\tnode.free()\n\t\t\n\t\tFmodServer.set_listener_number(1);\n\t\n\tfunc assert_listener_num(desiredValue: int):\n\t\tassert_eq(FmodServer.get_listener_number(), desiredValue, \"There should be \" + str(desiredValue) + \" listeners.\")\n\t\n\tfunc assert_listener_weight(listenerNum: int, desiredValue: float):\n\t\tassert_eq(FmodServer.get_listener_weight(listenerNum), desiredValue, str(listenerNum) + \" should have a weight of \" + str(desiredValue))\n\t\n\tfunc assert_no_object_attached_to_listener(desired_listener: int):\n\t\tassert_true(FmodServer.get_object_attached_to_listener(desired_listener) == null, \"Listener \" + str(desired_listener) + \" should not have any object attached\")\n"
  },
  {
    "path": "demo/test/unit/test_listener.gd.uid",
    "content": "uid://dptymuaf8rbid\n"
  },
  {
    "path": "demo/test/unit/test_sound.gd",
    "content": "extends \"res://addons/gut/test.gd\"\n\nclass TestSound:\n\textends \"res://addons/gut/test.gd\"\n\t\n\tvar sound: FmodSound\n\tvar music: FmodSound\n\tvar sound_file = \"res://assets/Sounds/doorOpen_1.ogg\"\n\tvar music_file = \"res://assets/Music/jingles_SAX07.ogg\"\n\t\n\tfunc before_all():\n\t\tFmodServer.load_file_as_sound(sound_file)\n\t\tFmodServer.load_file_as_music(music_file)\n\t\tsound = FmodServer.create_sound_instance(sound_file)\n\t\tmusic = FmodServer.create_sound_instance(music_file)\n\t\n\tfunc after_all():\n\t\tsound.release()\n\t\tmusic.release()\n\t\tFmodServer.unload_file(sound_file)\n\t\tFmodServer.unload_file(music_file)\n\t\n\tfunc test_assert_set_volume():\n\t\tvar desired_value: float = 2.0\n\t\tsound.volume = desired_value\n\t\tassert_eq(sound.volume, desired_value, \"Sound volume should be 2\")\n\t\tmusic.volume = desired_value\n\t\tassert_eq(music.volume, desired_value, \"Sound volume should be 2\")\n\t\n\tfunc test_assert_set_pitch():\n\t\tvar desired_value: float = 0.75\n\t\tsound.pitch = desired_value\n\t\tassert_eq(sound.pitch, desired_value, \"Sound pitch should be 0.75\")\n\t\tmusic.pitch = desired_value\n\t\tassert_eq(music.pitch, desired_value, \"Sound pitch should be 0.75\")\t\n\t\n\tfunc test_assert_playing():\n\t\tsound.play()\n\t\tassert_true(sound.is_playing(), \"Sound should be playing\")\n\t\tmusic.play()\n\t\tassert_true(music.is_playing(), \"Sound should be playing\")\n"
  },
  {
    "path": "demo/test/unit/test_sound.gd.uid",
    "content": "uid://bwtkh2jv2lk6w\n"
  },
  {
    "path": "demo/test/unit/test_vca.gd",
    "content": "extends \"res://addons/gut/test.gd\"\n\nclass TestVCA:\n\textends \"res://addons/gut/test.gd\"\n\t\n\tvar sprite: Sprite2D = Sprite2D.new()\n\t\n\tvar banks := Array()\n\t\n\tfunc before_all():\n\t\t# load banks\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Master.strings.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Master.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Music.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\t# warning-ignore:return_value_discarded\n\t\tbanks.append(\n\t\t\tFmodServer.load_bank(\"res://assets/Banks/Vehicles.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\t)\n\t\tFmodServer.set_listener_number(1)\n\t\tget_tree().get_root().add_child(sprite)\n\t\tFmodServer.add_listener(0, sprite)\n\t\n\tfunc after_all():\n\t\tFmodServer.remove_listener(0, sprite)\n\t\n\tfunc test_assert_valid_paths():\n\t\tassert_true(FmodServer.check_vca_path(\"vca:/Environment\"), \"vca:/Environment should be present\")\n\t\tassert_true(FmodServer.check_vca_path(\"vca:/Player\"), \"vca:/Player should be present\")\n\t\tassert_true(FmodServer.check_vca_path(\"vca:/Equipment\"), \"vca:/Equipment should be present\")\n\t\n\tfunc test_assert_invalid_path():\n\t\tassert_false(FmodServer.check_vca_path(\"vca:/undefined\"), \"Invalid vca should not be present\")\n\t\n\tfunc test_assert_volume():\n\t\t_test_assert_volume(false)\n\t\t_test_assert_volume(true)\n\t\n\tfunc _test_assert_volume(is_guid: bool):\n\t\tvar desired_value: float = 1.0\n\t\tvar environment_vca = FmodServer.get_vca_from_guid(\"{3f0b7d64-e765-400e-ae74-c2d973ad4ca1}\") if is_guid else FmodServer.get_vca(\"vca:/Environment\")\n\t\tassert_eq(environment_vca.volume, desired_value, \"VCA volume should be \" + str(desired_value))\n\t\tdesired_value = 0.5\n\t\tenvironment_vca.volume = desired_value\n\t\tassert_eq(environment_vca.volume, desired_value, \"VCA volume should be \" + str(desired_value))\n\t\tdesired_value = 1.0\n\t\tenvironment_vca.volume = desired_value\n"
  },
  {
    "path": "demo/test/unit/test_vca.gd.uid",
    "content": "uid://dfj4ym020lnkg\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "site/\np3/"
  },
  {
    "path": "docs/build.sh",
    "content": "#! /usr/bin/env bash\nset -e\nBASEDIR=$(dirname \"$0\")\nVIRTUALENV_DIR=\"$BASEDIR/p3\"\n\nif [[ ! -d \"$VIRTUALENV_DIR\" ]]; then\n    python -m pip install virtualenv\n    python -m virtualenv -p python3 \"$VIRTUALENV_DIR\"\nfi\nsource \"$VIRTUALENV_DIR/bin/activate\"\npip install -r requirements.txt\nmkdocs build\n"
  },
  {
    "path": "docs/mkdocs.yml",
    "content": "site_name: Godot Fmod GdExtension\ndocs_dir: src/doc\n\nrepo_name: 'utopia-rise/fmod-gdextension'\nrepo_url: 'https://github.com/utopia-rise/fmod-gdextension'\n\ntheme:\n  name: material\n  favicon: assets/favicon.ico\n  palette:\n    - scheme: default\n      toggle:\n        icon: material/toggle-switch-off-outline\n        name: Switch to dark mode\n    - scheme: slate\n      toggle:\n        icon: material/toggle-switch\n        name: Switch to light mode\n  logo: assets/fmod-gdextension-logo.png\n  features:\n    - navigation.tabs\n    - navigation.sections\n    - navigation.top\n\n\nextra:\n  social:\n    - icon: fontawesome/brands/github-alt\n      link: https://github.com/utopia-rise\n\nmarkdown_extensions:\n  - pymdownx.highlight:\n      linenums: true\n  - pymdownx.inlinehilite\n  - pymdownx.highlight\n  - pymdownx.superfences\n  - pymdownx.tabbed\n  - admonition\n  - meta\n\nextra_javascript:\n  - https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js\n  - js/init.js\nextra_css:\n  - https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/default.min.css\n"
  },
  {
    "path": "docs/requirements.txt",
    "content": "mkdocs-material==7.1.0\nmkdocs==1.3.0\n"
  },
  {
    "path": "docs/run.sh",
    "content": "#! /usr/bin/env bash\nset -e\nBASEDIR=$(dirname \"$0\")\nVIRTUALENV_DIR=\"$BASEDIR/p3\"\n\nif [[ ! -d \"$VIRTUALENV_DIR\" ]]; then\n    python -m pip install virtualenv\n    python -m virtualenv -p python3 \"$VIRTUALENV_DIR\"\nfi\nsource \"$VIRTUALENV_DIR/bin/activate\"\npip install -r requirements.txt\nmkdocs serve\n"
  },
  {
    "path": "docs/src/doc/advanced/1-compiling.md",
    "content": "# Compiling from sources\n\nClone project with `--recurse_submodules`.\n\n## Typical Project structure\n\n```\n└── Project root\n    ├── libs\n    |   └── fmod\n    |       └── {platform}\n    |           └── specific platform fmod api goes here\n    └── fmod-gdextension (this repo, consider using it as a submodule of you GDExtensions repo)\n        ├── CMakeLists.txt (Here for CLion)\n        ├── LICENSE\n        ├── README.md\n        ├── SConstruct (here to build on Windows, Linux, OSX and IOS\n        ├── godot-cpp (gdextension bindings, submodule)\n        └── src\n```\n\nYou are supposed to put fmod libraries under `libs/fmod/{platform}`, according to the platforms you want to support.  \n`libs` folder should be a brother to plugin repo in hierarchy.\n\n`CMakeLists` is here for CLion ide, as we are used to JetBrains tools. Unfortunately, CLion does not currently support\nSconstruct.\n\n## Compiling\n\nIn order to compile you should run `scons` command, as for all gdextension projects.\n\n```\nscons platform=<macos|linux|windows|ios|android> target=<editor|template_debug|template_release> --jobs=<desired number of threads>\n```\n\nIf `target` is `editor` or `template_debug`, debug symbols will be generated.\n\n### Additional Instructions\n\nOn Windows, you need to pass the `arch` argument with values `x86_64` or `x86_32`.\n\nExample:\n```\nscons platform=windows arch=x86_64 target=editor\n```\n\nYou can also specify the FMOD directory as an argument, so you don't need to place the `libs` outside the project root.\n\nExample:\n```\nscons platform=windows arch=x86_64 target=editor fmod_lib_dir=libs/\n```\n"
  },
  {
    "path": "docs/src/doc/index.md",
    "content": "[![GitHub](https://img.shields.io/github/license/utopia-rise/fmod-gdextension?style=flat-square)](LICENSE)\n\n![fmod-gdextension-image]\n\nGodot C++ GDExtension for Godot 4 that provides an integration for the FMOD Studio API.\n\nFMOD is an audio engine and middleware solution for interactive audio in games. It has been the audio engine behind many\ntitles such as Transistor, Into the Breach and Celeste. [More on FMOD's website](https://www.fmod.com/).\n\nThis GDExtension exposes most of the Studio API functions to Godot's GDScript and also provides helpers ands nodes for\nperforming common functions like attaching Studio events to Godot nodes and playing 3D/positional audio. Feel free to\ntweak/extend it based on your project's needs.\n\n[fmod-gdextension-image]: ./assets/fmod-gdextension-logo.png\n"
  },
  {
    "path": "docs/src/doc/user-guide/1-install.md",
    "content": "# Setup guide\n\n## 1 - Install addon folder\n\nWe provide releases in github repository. \n\nYou can download `addon.zip` from this [page](https://github.com/utopia-rise/fmod-gdextension/releases), then unzip it \nand copy its content to the `addons` directory of your Godot project.\n\n## 2 - Activate the plugin in Godot\n\nOpen your Godot project, go to your project settings and select the `Plugins` tab.\nYou should see the line `FMOD GDExtension` and a checkbox. Enable it.\nAfter this, you should be able to create Fmod nodes, use the bank explorer and write scripts using `FmodServer`\n\n## 3 - Android specific\n\nAndroid exports require a Godot Android plugin.  \n\nDownload android build template from project menu:  \n![install-android-build-template]  \n\nMake sure the `Use Gradle Build` option is activated in your Android export:  \n![android-extension-export-enable]\n\n!!! warning\n    We curently only support armv8 architecture for android export.  \n    In order to get support for x86, you should build plugin on your own. \n\n[android-extension-export-enable]: ./assets/android-export-enable-extension.png\n[install-android-build-template]: ./assets/install-android-build-template.png"
  },
  {
    "path": "docs/src/doc/user-guide/2-initialization.md",
    "content": "# Initialization\n\nWhen you first add fmod addon to your godot project, you need to setup fmod options within projects parameters.  \nParameters are split in several categories:  \n- General  \n- Software Format  \n- Dsp  \n- 3d Settings  \n\n## General\n\n- \"Auto Initialize\": If `true`, will start fmod on engine startup.\n- \"Channel Count\": Maximum number of Channel objects available for playback, also known as virtual voices. Virtual \nvoices will play with minimal overhead, with a subset of 'real' voices that are mixed, and selected based on priority\nand audibility. See the Virtual Voices guide for more information.\n- \"Live update\": Enable live update.\n- \"Memory Tracking\": Enables detailed memory usage statistics. Increases memory footprint and impacts performance.\nSee Studio::Bus::getMemoryUsage and Studio::EventInstance::getMemoryUsage for more information.\nImplies FMOD_INIT_MEMORY_TRACKING.\n- Default Listener count: set max listener count (should be between 1 and 8).  \n- Should Load by Name: If true will load events and parameters by name instead of id when using fmod nodes.\n\n![general-tab]\n\n## Software Format\n\n- \"Sample Rate\": Sample rate of the mixer. Range: [8000, 192000] Units: Hertz Default: 48000\n- \"Speaker Mode\": Speaker setup of the mixer.\n[FMOD_SPEAKERMODE](https://www.fmod.com/docs/2.03/api/core-api-common.html#fmod_speakermode)\n- \"Raw Speaker Count\": Number of speakers for\n[FMOD_SPEAKERMODE_RAW](https://www.fmod.com/docs/2.03/api/core-api-common.html#fmod_speakermode_raw) mode.\nRange: [0, [FMOD_MAX_CHANNEL_WIDTH](https://www.fmod.com/docs/2.03/api/core-api-common.html#fmod_max_channel_width)]\n\n![software-format-tab]\n\n## Dsp\n\n- \"Dsp Buffer Size\": The mixer engine block size. Use this to adjust mixer update granularity. Units: Samples Default:\n1024\n- \"Dsp Buffer Count\": The mixer engine number of buffers used. Use this to adjust mixer latency. Default: 4\n\n![dsp-tab]\n\n## 3d Settings\n\n- \"Doppler Scale\": A scaling factor for doppler shift. Default 1.\n- \"Distance Factor\": A factor for converting game distance units to FMOD distance units. Default 1.  \n  !!! warning\n  In 2D this value represents pixels, so you should set it to the number of pixel for your world meter (If your world\n  meter is 64px, set it to 64). In 3D this represents meter, so we recommend to set it to 1.\n- \"Rolloff Scale\": A scaling factor for distance attenuation. When a sound uses a roll-off mode other than\nFMOD_3D_CUSTOMROLLOFF and the distance is greater than the sound's minimum distance, the distance is scaled by the\nroll-off scale.\n\n![3d-tab]\n\n## Fmod explorer\n\nWhen all is setup you can explore your project's banks using `Fmod Explorer`.  \n![fmod-explorer]\n\n\n[general-tab]: ./assets/parameter-general.png\n[software-format-tab]: ./assets/parameters-software-format.png\n[dsp-tab]: ./assets/parameters-dsp.png\n[3d-tab]: ./assets/parameters-3d.png\n[fmod-explorer]: ./assets/fmod-explorer.png"
  },
  {
    "path": "docs/src/doc/user-guide/3-using-fmod-plugin.md",
    "content": "# Using FMOD plugin\n\nThis documentation provides detailed guidance on how to use the FMOD GDExtension plugin for integrating FMOD into your\nGodot projects.\n\nThe plugin offers two main approaches for interacting with FMOD:\n1. **Using FMOD Nodes**: Predefined nodes that simplify common FMOD tasks.\n2. **Using the `FmodServer` API**: A singleton that gives you direct access to FMOD's core and system APIs for more\nadvanced or customized workflows.\n\n#### Core Component: `FmodServer`\nAt the heart of the plugin is the `FmodServer` singleton. It serves as a bridge to FMOD's powerful API, allowing you to\ncontrol audio behavior programmatically. FMOD nodes are built on top of this singleton, offering higher-level\nabstractions for convenience.\n\n## Summary\n- Loading banks\n    - [Using node](4-loading-banks.md#fmodbankloader-node)\n    - [Using FmodServer](4-loading-banks.md#fmodserver-api)\n- Playing events\n    - [Using node](5-playing-events.md#fmodeventemitter-nodes)\n    - [Using FmodServer](5-playing-events.md#fmodserver-api)\n- Listeners\n    - [Using node](6-listeners.md#fmod-listener-nodes)\n    - [Using FmodServer](6-listeners.md#using-fmodserver-api)\n- [Playing sounds](7-playing-sounds.md)\n- [Other low level examples](8-other-low-level-examples.md)\n\n\n"
  },
  {
    "path": "docs/src/doc/user-guide/4-loading-banks.md",
    "content": "# Loading banks\n\nIn this guide we'll explore how to load banks within your game.  \n\n## FmodBankLoader node\n\n`FmodBankLoader` is in charge of loading banks when entering the scene. You should place it, in the scene hierarchy,\nbefore all other fmod nodes using this bank. Banks are unloaded on exit tree.  \n\nBanks are `RefCounted`, so several `FmodBankLoader` can share same banks.\n\nIf you want to load your bank when starting game and keep them loaded, use this node and add it as **autoload** node.  \n\nYou can add banks with fmod project explorer, using the `+` button with bank icon, or manually add a bank using bottom\nline edit. You can also remove and re-order banks:  \n![fmod-bank-image]\n\n!!! warning\nMake sure to first place `Master.strings.bank` first, and `Master.bank` in second. Those banks are dependencies needed\nby other banks. So if you don't load them first, you won't be able to load other banks.\n\n## FmodServer api\n\nYou can also load banks using `FmodServer` api.  \nFor this purpose, you should use `load_bank` method of `FmodServer` singleton.  \n\nHere is an example:  \n```gdscript\nvar banks := Array()\n\nfunc _ready():\n    banks.append(FmodServer.load_bank(\"res://Master.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL))\n    banks.append(FmodServer.load_bank(\"res://Master.strings.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL))\n    banks.append(FmodServer.load_bank(\"res://Music.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL))\n```\n\n!!! warning\nAs banks are `RefCounted`, don't forget to store them. Otherwise reference counter will be directly decremented leading\nto unload of the bank.\n\n!!! warning\nMake sure to first load `Master.strings.bank`, and `Master.bank` in second. Those banks are dependencies needed by other\nbanks. So if you don't load them first, you won't be able to load other banks.\n\n[fmod-bank-image]: ./assets/fmod-bank.png"
  },
  {
    "path": "docs/src/doc/user-guide/5-playing-events.md",
    "content": "# Playing events\n\nIn this guide we'll explore how to play Fmod events.  \n\n## FmodEventEmitter nodes\n\n`FmodEventEmitter2D` and `FmodEventEmitter3D` are nodes which play Fmod events.\nEvents and their parameters are loaded by id or name according to [Fmod General settings](./2-initialization.md#general).\n\n![emitter-image]\n\n### Properties\n\nFirst, set the event that the emitter will play. You can set the name or guid of the event manually, or use the event selection button.  \n\nThen, there are a few options you can toggle:\n- *attached*: if `true`, the Fmod event's position will update alongside the node's position.  \n- *autoplay*: if `true`, the event will autoplay (if `false`, it will play when the `play()` function is called on it).  \n- *auto_release*: if `true`, the emitter node will be automatically freed when the event finishes playing.  \n- *allow_fadeout*: if `true`, the event will fade out when stopped.  \n- *preload_event*: if `true`, the event will be preloaded when the node is ready. \n\n#### Fmod parameters\n\nEvent emitters have dynamics properties corresponding to fmod parameters associated with the current event.  \nYou can set their values like any other godot float property.  \nFrom scripts, you can change them using `get` and `set` object's operators.  \nExample:\n```gdscript\nextends FmodEventEmitter2D\n\nfunc _process(_delta):\n\tif Input.is_action_pressed(\"engine_power_up\"):\n\t\tself[\"fmod_parameters/RPM\"] = self[\"fmod_parameters/RPM\"] + 10\n\tif Input.is_action_pressed(\"engine_power_down\"):\n\t\tself[\"fmod_parameters/RPM\"] = self[\"fmod_parameters/RPM\"] - 10\n```  \nTo easily retrieve fmod parameters properties path, you can use godot's `Copy Property Path` functionality.  \n\nYou can also use following methods:  \n- `get_parameter`: Get the value of a parameter using its name.  \n- `set_parameter`: Set the value of a parameter using its name.  \n- `get_parameter_by_id`: Get the value of a parameter using its id.  \n- `set_parameter_by_id`: Set the value of a parameter using its id.  \n\nExample:  \n```gdscript\nemitter.set_parameter(\"RPM\", 1000)\nemitter.set_parameter_by_id(5864137074015534804, 1000)\n\nemitter.get_parameter(\"RPM\")\nemitter.get_parameter_by_id(5864137074015534804)\n```\n\n### Signals\n\n`FmodEventEmitter2D` and `FmodEventEmitter3D` emits signals:\n#### timeline_beat\nEmitted on fmod event's timeline beat callback.\nParameters (as dictionary):  \n- `beat`: Beat number within bar (starting from 1).  \n- `bar`: Bar number (starting from 1).  \n- `tempo`: Current tempo in beats per minute.  \n- `time_signature_upper`: Current time signature upper number (beats per bar).  \n- `time_signature_lower`: Current time signature lower number (beat unit).  \n- `position`: Position of the beat on the timeline in milliseconds.  \n\n#### timeline_marker:\nEmitted when fmod event timeline passes a named marker.\nParameters (as dictionary):  \n- `name`: Marker name.  \n- `position`: Position of the marker on the timeline in milliseconds.  \n\n#### started\nEmitted when event starts and was not playing.\nNo parameters.\n\n#### restart\nEmitted when event starts and was playing.\nNo parameters.\n\n#### stopped\nEmitted when event has stopped.\nNo parameters.\n\n### Methods\n\n#### play\n\nStarts the event.\n\n*parameters:*  \n- `restart_if_playing`: If true, will restart event if it is already playing. Default value: `true`.  \n\n#### play_one_shot\n\nStarts a one shot instance of the event that will not be managed by the emitter. Useful for short SFX.\n\n#### stop\nStops the event.\n\n#### Set event methods\n\nBoth methods `set_event_name` and `set_event_guid` changes the event played by the emitter.  \nThose methods stop and unload the current playing event. It also clear the current parameters values.  \nIf emitter option `preload_event` is true, it will load the new event.  \nIf emitter option `autoplay` is true, it will load and play the new event.\n\n#### set_volume\n\nThis sets the emitter volume. If the emitter doe not have any loaded event yet, this will be applied to the future \nevent.\n\n### Programmers callbacks\n\nTo use a programmer callback, you must first load the bank with the\naudio table used by your event containing the programmer callback.  \nYou can then use `set_programmer_callback` on the event to specify the\nkey from audio table to use.\n\n```gdscript\nvar event_emitter = FmodEventEmitter2D.new()\nevent_emitter.event_guid = \"{9aa2ecc5-ea4b-4ebe-85c3-054b11b21dcd}\" # event:/Character/Dialogue from sfx bank.\nevent_emitter.autoplay = true\nevent_emitter.set_programmer_callback(\"welcome\") # welcome key from audio table in Dialogue_EN.bank, Dialogue_JP.bank and Dialogue_CN.bank. One of those bank should be loaded.\nadd_child(event_emitter)\n```\n\n## FmodServer api\n\nYou can also use `FmodServer` api to play events. Here is an example script:  \n```gdscript\nextends Sprite2D\n\nvar isPlaying: bool = true\nvar event: FmodEvent = null\n\n# Called when the node enters the scene tree for the first time.\nfunc _ready():\n\tevent = FmodServer.create_event_instance(\"event:/Vehicles/Car Engine\")\n\tevent.set_2d_attributes(self.global_transform)\n\tevent.set_parameter_by_name(\"RPM\", 600)\n\tevent.volume = 2\n\tevent.start()\n\t\n# warning-ignore:unused_argument\nfunc _process(_delta):\n\tif Input.is_action_just_pressed(\"space\"):\n\t\tisPlaying = !isPlaying\n\t\tif(isPlaying):\n\t\t\tprint(\"Mower playing\")\n\t\t\tevent.paused = false\n\t\telse:\n\t\t\tprint(\"Mower paused\")\n\t\t\tevent.paused = true\n\telif Input.is_action_just_pressed(\"kill_event\"):\n\t\tself.queue_free()\n```\n\nIn this script we create an instance of `FmodEvent` by calling `FmodServer`.  \nWe then set its position attribute to the node's transform, set its `RPM` parameter to `600` and its `volume` to `2`. \nWe then start it.  \nIn the `_process` method, we pause the event if we press `space` action.  \n\nHere is another with setting callback:  \n```gdscript\nextends Area2D\n\nvar event: FmodEvent = null\nvar icon: Sprite2D\n\n# Called when the node enters the scene tree for the first time.\nfunc _ready():\n\tevent = FmodServer.create_event_instance(\"event:/Music/Level 02\")\n\tevent.set_callback(Callable(self, \"change_color\"), FmodServer.FMOD_STUDIO_EVENT_CALLBACK_ALL)\n\tbody_entered.connect(enter)\n\tbody_exited.connect(leave)\n\tevent.start()\n\tevent.paused = true\n\n# warning-ignore:unused_argument\nfunc enter(_area):\n\tprint(\"enter\")\n\tevent.paused = false\n\t\n# warning-ignore:unused_argument\nfunc leave(_area):\n\tprint(\"leave\")\n\tevent.paused = true\n\n# warning-ignore:unused_argument\nfunc change_color(_dict: Dictionary, type: int):\n\tif type == FmodServer.FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_BEAT:\n\t\t$icon.self_modulate = Color(randf_range(0,1), randf_range(0,1), randf_range(0,1), 1)\n```\n\nIn this script we create an `FmodEvent` and set a callback on it. Each time the event emits the callback we change the \nnode's color.  \n\n### Programmers callback\n\nIn order to play a programmer callback you must load the bank with audio table concerned by the programmer callback.  \nthen you can use `set_programmer_callback` to specify key from audio table to use.\n\n```gdscript\nFmodServer.load_bank(\"res://assets/Banks/Dialogue_EN.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\nvar event_instance = FmodServer.create_event_instance(\"event:/Character/Dialogue\") # event from sfx bank.\nevent_instance.set_programmer_callback(\"welcome\") # welcome key in audio table from Dialogue bank.\nevent_instance.start()\n```\n\n[emitter-image]: ./assets/emitter.png\n"
  },
  {
    "path": "docs/src/doc/user-guide/6-listeners.md",
    "content": "# Listeners\n\nIn this guide we'll cover how to add FMOD listeners to your godot project.\n\n## Fmod listener nodes\n\n`FmodListener2D` and `FmodListener3D` are nodes to add a FMOD listener and bind it to them, this means position of the \nFMOD listener will match position of the `FmodListener` node.  \n\n![listener-image]\n\n### Properties:\n\n#### listener_index\nIndex of the listener. You can have up to 8 listener concurrently.\n#### is_locked\nIf `true`, listener will not update its position according to node's position.\n#### weight\nUsed to compute the relative contribution to the final sound.\n\n## Using FmodServer api\n\nYou can add and remove listeners using `FmodServer` api:  \n\n```gdscript\nfunc _ready():\n\tFmodServer.add_listener(0, self)\n```\n\nIn this script we set a listener with index `0` and attached it to the current node.  \n\n```gdscript\nfunc _ready():\n\tFmodServer.remove_listener(0, self)\n```\n\nIn this script we remove the listener with index `0` attached to the current node.  \n\nYou can also set the listener weight using `FmodServer`:  \n```gdscript\nFmodServer.set_system_listener_weight(0, 1.5)\n```\n\nIn this script we set the weight of the listener with index `0` to `1.5`.\n\n\n[listener-image]: ./assets/listeners.png"
  },
  {
    "path": "docs/src/doc/user-guide/7-playing-sounds.md",
    "content": "# Playing sounds\n\nYou can play sounds using `FmodServer` api.  \n\nHere is an example:  \n```gdscript\nextends Area2D\n\n# Declare member variables here. Examples:\n# var a = 2\nvar music: FmodSound = null\n\n# Called when the node enters the scene tree for the first time.\nfunc _ready():\n\tFmodServer.load_file_as_music(\"res://assets/Music/jingles_SAX07.ogg\")\n# warning-ignore:return_value_discarded\n\tbody_entered.connect(enter)\n# warning-ignore:return_value_discarded\n\tbody_exited.connect(leave)\n\n# warning-ignore:unused_argument\nfunc enter(_area):\n\tprint(\"enter\")\n\tmusic = FmodServer.create_sound_instance(\"res://assets/Music/jingles_SAX07.ogg\")\n\tmusic.play()\n\t\n# warning-ignore:unused_argument\nfunc leave(_area):\n\tprint(\"leave\")\n\tmusic.release()\n\nfunc _exit_tree():\n\tFmodServer.unload_file(\"res://assets/Music/jingles_SAX07.ogg\")\n```\n\nThis script loads an ogg file. Then it connects godot's `body_entered` and `body_exited` signals.  \nWhen we enter the node, it will create a sound instance using `create_sound_instance` method and play it.  \nWhen we exit the node, it will release the sound.  \nAt the end, we the node is removed from godot tree, it unloads the sound file."
  },
  {
    "path": "docs/src/doc/user-guide/8-other-low-level-examples.md",
    "content": "# Other FmodServer api examples\n\n## Muting all event\n\nYou can mute all event using `mute_all_events`. This will mute the master bus.\n\n```gdscript\nfunc _ready():\n\tFmodServer.set_software_format(0, FmodServer.FMOD_SPEAKERMODE_STEREO, 0)\n\tFmodServer.init(1024, FmodServer.FMOD_STUDIO_INIT_LIVEUPDATE, FmodServer.FMOD_INIT_NORMAL)\n\tFmodServer.set_sound_3d_settings(1.0, 64.0, 1.0)\n\t\n\t# load banks\n\tFmodServer.load_bank(\"res://Master.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\tFmodServer.load_bank(\"res://Master.strings.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\tFmodServer.load_bank(\"res://Music.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\n\t# register listener\n\tFmodServer.add_listener(0, self)\n\t\n\t# play some events\n\tFmodServer.play_one_shot(\"event:/Music/Level 02\", self)\n\tvar my_music_event = FmodServer.create_event_instance(\"event:/Music/Level 01\")\n\tFmodServer.start_event(my_music_event)\n\tvar t = Timer.new()\n\tt.set_wait_time(3)\n\tt.set_one_shot(true)\n\tself.add_child(t)\n\tt.start()\n\tyield(t, \"timeout\")\n\tFmodServer.mute_all_events();\n\tt = Timer.new()\n\tt.set_wait_time(3)\n\tt.set_one_shot(true)\n\tself.add_child(t)\n\tt.start()\n\tyield(t, \"timeout\")\n\tFmodServer.unmute_all_events()\n```\n\n## Pausing all events\n\n```gdscript\nfunc _ready():\n\t# set up FMOD\n\tFmodServer.set_software_format(0, FmodServer.FMOD_SPEAKERMODE_STEREO, 0)\n\tFmodServer.init(1024, FmodServer.FMOD_STUDIO_INIT_LIVEUPDATE, FmodServer.FMOD_INIT_NORMAL)\n\tFmodServer.set_sound_3d_settings(1/0, 64.0, 1.0)\n\t\n\t# load banks\n\tFmodServer.load_bank(\"res://Master.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\tFmodServer.load_bank(\"res://Master.strings.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\tFmodServer.load_bank(\"res://Music.bank\", FmodServer.FMOD_STUDIO_LOAD_BANK_NORMAL)\n\t\n\t# register listener\n\tFmodServer.add_listener(0, self)\n\t\n\t# play some events\n\tFmodServer.play_one_shot(\"event:/Music/Level 02\", self)\n\tvar my_music_event = FmodServer.create_event_instance(\"event:/Music/Level 01\")\n\tFmodServer.start_event(my_music_event)\n\tvar t = Timer.new()\n\tt.set_wait_time(3)\n\tt.set_one_shot(true)\n\tself.add_child(t)\n\tt.start()\n\tyield(t, \"timeout\")\n\tFmodServer.pause_all_events(true)\n\tt = Timer.new()\n\tt.set_wait_time(3)\n\tt.set_one_shot(true)\n\tself.add_child(t)\n\tt.start()\n\tyield(t, \"timeout\")\n\tFmodServer.pause_all_events(false)\n```\n\n## Changing the default audio output device\n\nBy default, FMOD will use the primary audio output device as determined by the operating system. This can be changed at runtime, ideally through your game's Options Menu.\n\nHere, `get_available_drivers()` returns an Array which contains a Dictionary for every audio driver found. Each Dictionary contains fields such as the name, sample rate\nand speaker config of the respective driver. Most importantly, it contains the id for that driver.\n\n```gdscript\n# retrieve all available audio drivers\nvar drivers = FmodServer.get_available_drivers()\n # change the audio driver\n# you must pass in the id of the respective driver\nFmodServer.set_driver(id)\n # retrieve the id of the currently set driver\nvar id = FmodServer.get_driver()\n```\n\n## Reducing audio playback latency\n\nYou may encounter that the audio playback has some latency. This may be caused by the DSP buffer size. You can change the value **before** initialisation to adjust it:\n```gdscript\nFmodServer.set_dsp_buffer_size(512, 4)\n# retrieve the buffer length\nFmodServer.get_dsp_buffer_length()\n# retrieve the number of buffers\nFmodServer.get_dsp_num_buffers()\n```\n\n## Profiling & querying performance data\n\n`get_performance_data` returns an object which contains current performance stats for CPU, Memory and File Streaming usage of both FMOD Studio and the Core System.\n\n```gdscript\n# called every frame\nvar perf_data = FmodServer.get_performance_data()\n\nprint(perf_data.CPU)\nprint(perf_data.memory)\nprint(perf_data.file)\n```\n"
  },
  {
    "path": "docs/src/doc/user-guide/9-plugins.md",
    "content": "# Plugins\n\n!!! warning\n    This feature is experimental. Please report any bug you find.\n\nThis addon supports FMOD plugins.  \nFirst you'll need to add plugins libraries (`.dll`, `.dylib`, `.so`, `.a`) in a project's folder.  \nIn this documentation we will use `assets/plugins` as an example.  \nIn this directory you have to create one directory per os, like:  \n```\nassets\n└───── plugins\n       ├────── windows\n       │       └────── .dll for windows goes here\n       ├────── linux\n       │       └────── .so for linux goes here\n       ├────── macos\n       │       └────── .dylib for macos goes here\n       ├────── android\n       │       ├────── x86_64\n       │       │       └────── .so for android x86 goes here\n       │       └────── arm64\n       │               └────── .so for android arm64 goes here\n       └────── ios\n               └────── .a for ios goes here\n```  \nIf there are some dependent libraries needed for a plugin, just place it next to plugin's library, all libraries in \nthose folders are exported.  \n\nIn order to add plugins you first need to create an `FmodPluginsSettings` resource in your project:  \n![plugins-create-settings-resource]  \nFirst you need to configure the directory containing the plugins libraries. In this example `res://assets/plugins/`\nThen you have to configure the plugin lists.  \nThere is two lists to configure:  \n- `Dynamic Plugin List` is used for all platforms except `iOS`\n- `Static Plugins Methods` is used for `iOS` only\n\nFor dynamic plugins you have to enter the name of the library for the plugin (without the lib prefix). As an example for \nsteam plugin it will be `phonon_fmod`.  \nIn static plugins methods you have to place the methods from the documentation of your plugin. As and example for steam \nplugin it will be:  \n- `FMOD_SteamAudio_Spatialize_GetDSPDescription`, with DSP type\n- `FMOD_SteamAudio_MixerReturn_GetDSPDescription`, with DSP type\n- `FMOD_SteamAudio_Reverb_GetDSPDescription`, with DSP type\n\nHere is what the configuration looks like for this example:  \n![fmod-plugins-settings-resource]\n\nThen you have to set the path of this resource in project's settings, in the `Fmod/Plugins` section:  \n![plugins-project-settings]\n\n\n[plugins-create-settings-resource]: ./assets/plugins-create-settings-resource.png\n[fmod-plugins-settings-resource]: ./assets/fmod-plugins-settings-resource.png\n[plugins-project-settings]: ./assets/plugins-project-settings.png"
  },
  {
    "path": "get_fmod.py",
    "content": "import requests\nimport sys\n\nargv = sys.argv[1:] \n\nuser = argv[0]\npassword = argv[1]\nplatform = argv[2]\nfmod_version = argv[3]\n\nfmodlink = \"https://www.fmod.com/api-login\"\n\nif platform == 'linux':\n    # linux\n    filename = f'fmodstudioapi{fmod_version}linux.tar.gz'\n    downloadlink = f'https://www.fmod.com/api-get-download-link?path=files/fmodstudio/api/Linux/&filename=fmodstudioapi{fmod_version}linux.tar.gz&user='\nelif platform == 'macos':\n    # OS X\n    filename = f'fmodstudioapi{fmod_version}osx.dmg'\n    downloadlink = f'https://www.fmod.com/api-get-download-link?path=files/fmodstudio/api/Mac/&filename=fmodstudioapi{fmod_version}mac-installer.dmg&user='\nelif platform == 'windows':\n    # Windows...\n    filename = f'fmodstudioapi{fmod_version}win-installer.exe'\n    downloadlink = f'https://www.fmod.com/api-get-download-link?path=files/fmodstudio/api/Windows/&filename=fmodstudioapi{fmod_version}win-installer.exe&user='\nelif platform == 'android':\n    # Android...\n    filename = f'fmodstudioapi{fmod_version}android.tar.gz'\n    downloadlink = f'https://www.fmod.com/api-get-download-link?path=files/fmodstudio/api/Android/&filename=fmodstudioapi{fmod_version}android.tar.gz&user='\nelif platform == 'ios':\n    # iOS...\n    filename = f'fmodstudioapi{fmod_version}ios.dmg'\n    downloadlink = f'https://www.fmod.com/api-get-download-link?path=files/fmodstudio/api/iOS/&filename=fmodstudioapi{fmod_version}ios-installer.dmg&user=$1'\n\ndownloadlink += user\n\n# First login and get a token!\nresponse = requests.post(fmodlink, auth = (user, password)).json()\ntoken = response[\"token\"]\n\nprint(\"Received token from FMOD login API.\")\n\n# Next request a download link using the token!\nresponse = requests.get(downloadlink, headers = {\"Authorization\": f\"Bearer {token}\"}).json()\nurl = response[\"url\"]\n\n# Download FMOD!\nresponse = requests.get(url, allow_redirects=True)\nopen(filename, 'wb').write(response.content)\n\nprint(\"Downloading FMOD using the requested download link.\")\nprint(response)"
  },
  {
    "path": "jni/Application.mk",
    "content": "# Application.mk\nAPP_STL:=c++_shared\nAPP_ABI:=arm64-v8a armeabi-v7a\n"
  },
  {
    "path": "src/callback/event_callbacks.cpp",
    "content": "#include \"fmod_studio.hpp\"\n#include <studio/fmod_event.h>\n#include \"fmod_server.h\"\n\n#include <callback/event_callbacks.h>\n\n#include <variant/callable.hpp>\n#include <variant/dictionary.hpp>\n\nnamespace Callbacks {\n\n    FMOD_RESULT F_CALL event_callback(FMOD_STUDIO_EVENT_CALLBACK_TYPE type, FMOD_STUDIO_EVENTINSTANCE* event, void* parameters) {\n        auto* instance = reinterpret_cast<FMOD::Studio::EventInstance*>(event);\n        godot::FmodEvent* event_instance;\n        instance->getUserData((void**) &event_instance);\n        if (event_instance) {\n            if (type == FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND) {\n                const godot::String& sound_key {event_instance->get_programmers_callback_sound_key()};\n                FMOD_STUDIO_SOUND_INFO sound_info {godot::FmodServer::get_singleton()->get_sound_info(sound_key)};\n                FMOD::Sound* sound {\n                        godot::FmodServer::get_singleton()->create_sound(sound_info, FMOD_LOOP_NORMAL | FMOD_CREATECOMPRESSEDSAMPLE | FMOD_NONBLOCKING)\n                };\n\n                auto* props { reinterpret_cast<FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES*>(parameters) };\n\n                props->sound = (FMOD_SOUND*) sound;\n                props->subsoundIndex = sound_info.subsoundindex;\n\n                return FMOD_OK;\n            }\n            if (type == FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND) {\n                auto* props { reinterpret_cast<FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES*>(parameters) };\n                auto* sound {(FMOD::Sound*) props->sound};\n\n                ERROR_CHECK(sound->release());\n\n                return FMOD_OK;\n            }\n\n            godot::Dictionary dictionary;\n            if (type == FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_MARKER) {\n                auto* props { reinterpret_cast<FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES*>(parameters) };\n                dictionary[\"name\"] = props->name;\n                dictionary[\"position\"] = props->position;\n            } else if (type == FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_BEAT) {\n                auto* props { reinterpret_cast<FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES*>(parameters) };\n                dictionary[\"beat\"] = props->beat;\n                dictionary[\"bar\"] = props->bar;\n                dictionary[\"tempo\"] = props->tempo;\n                dictionary[\"time_signature_upper\"] = props->timesignatureupper;\n                dictionary[\"time_signature_lower\"] = props->timesignaturelower;\n                dictionary[\"position\"] = props->position;\n            }\n            const godot::Callable& callback {event_instance->get_callback()};\n            if (!callback.is_null() && callback.is_valid()) {\n                godot::FmodServer::get_singleton()->add_callback(\n                        {\n                            type,\n                            callback,\n                            dictionary\n                        }\n                );\n            }\n        }\n\n        return FMOD_OK;\n    }\n}// namespace Callbacks\n"
  },
  {
    "path": "src/callback/event_callbacks.h",
    "content": "#ifndef GODOTFMOD_GODOT_FMOD_CALLBACK_H\n#define GODOTFMOD_GODOT_FMOD_CALLBACK_H\n\n#include <fmod_common.h>\n#include <fmod_studio_common.h>\n\nnamespace Callbacks {\n    FMOD_RESULT F_CALL event_callback(FMOD_STUDIO_EVENT_CALLBACK_TYPE type, FMOD_STUDIO_EVENTINSTANCE* event, void* parameters);\n}// namespace Callbacks\n\n#endif// GODOTFMOD_GODOT_FMOD_CALLBACK_H\n"
  },
  {
    "path": "src/callback/file_callbacks.cpp",
    "content": "#include \"file_callbacks.h\"\n\nnamespace Callbacks {\n\n    GodotFileRunner* GodotFileRunner::get_singleton() {\n        static GodotFileRunner singleton;\n        return &singleton;\n    }\n\n    void GodotFileRunner::queueReadRequest(FMOD_ASYNCREADINFO* request, ReadPriority priority) {\n        // High-priority requests have to be processed first.\n        if (priority == ReadPriority::HIGH) {\n            // lock so we can't add and remove elements from the queue at the same time.\n            std::lock_guard<std::mutex> lk(read_mut);\n            requests.push_front(request);\n        } else {\n            // lock so we can't add and remove elements from the queue at the same time.\n            std::lock_guard<std::mutex> lk(read_mut);\n            requests.push_back(request);\n        }\n        read_cv.notify_one();\n    }\n\n    FMOD_RESULT GodotFileRunner::cancelReadRequest(FMOD_ASYNCREADINFO* request) {\n        // lock so we can't add and remove elements from the queue at the same time.\n        {\n            std::lock_guard<std::mutex> lk(read_mut);\n            if (requests.erase(request)) {\n                request->bytesread = 0;\n                request->done(request, FMOD_RESULT::FMOD_ERR_FILE_DISKEJECTED);\n                return FMOD_RESULT::FMOD_ERR_FILE_DISKEJECTED;\n            }\n        }\n\n        // We lock and check if the current request is the one being canceled.\n        // In this case, we wait until it's done.\n        {\n            std::unique_lock<std::mutex> lk(cancel_mut);\n            if (request == current_request) { cancel_cv.wait(lk); }\n        }\n\n        return FMOD_RESULT::FMOD_OK;\n    }\n\n    void GodotFileRunner::run() {\n        while (!stop) {\n            // waiting for the container to have one request\n            {\n                std::unique_lock<std::mutex> lk(read_mut);\n                read_cv.wait(lk, [this] { return !requests.is_empty() || stop; });\n            }\n\n            while (!requests.is_empty()) {\n                // lock so we can't add and remove elements from the queue at the same time.\n                // also store the current request so it cannot be canceled during processing.\n                {\n                    std::lock_guard<std::mutex> lk(read_mut);\n                    current_request = requests.front()->get();\n                    requests.pop_front();\n                }\n\n                // We get the Godot File object from the handle\n                GodotFileHandle* handle {reinterpret_cast<GodotFileHandle*>(current_request->handle)};\n                godot::Ref<godot::FileAccess> file {handle->file};\n\n                // update the position of the cursor\n                file->seek(current_request->offset);\n\n                // We read and store the requested data in an array.\n                godot::PackedByteArray buffer {file->get_buffer(current_request->sizebytes)};\n                int size {static_cast<int>(buffer.size())};\n                const uint8_t* data {buffer.ptr()};\n\n                // We copy the data to FMOD buffer\n                memcpy(current_request->buffer, data, size * sizeof(uint8_t));\n                current_request->bytesread = size;\n\n                // Remember to return an error if the end of the file is reached\n                FMOD_RESULT result = (size < current_request->sizebytes) ? FMOD_RESULT::FMOD_ERR_FILE_EOF : FMOD_RESULT::FMOD_OK;\n                current_request->done(current_request, result);\n\n                // Request no longer processed\n                {\n                    std::lock_guard<std::mutex> lk(cancel_mut);\n                    current_request = nullptr;\n                }\n                cancel_cv.notify_one();\n            }\n        }\n    }\n\n    void GodotFileRunner::start() {\n        stop = false;\n        fileThread = std::thread(&GodotFileRunner::run, this);\n    }\n\n    void GodotFileRunner::finish() {\n        stop = true;\n        // we need to notify the loop one last time, otherwise it will stay stuck in the wait method.\n        read_cv.notify_one();\n        fileThread.join();\n    }\n\n    FMOD_RESULT F_CALL godotFileOpen(const char* name, unsigned int* filesize, void** handle, void* userdata) {\n        godot::Ref<godot::FileAccess> access = godot::FileAccess::open(name, godot::FileAccess::ModeFlags::READ);\n\n        if (access->get_error() == godot::Error::OK) {\n            *filesize = access->get_length();\n            GodotFileHandle* fileHandle {new GodotFileHandle {access}};\n            *handle = reinterpret_cast<void*>(fileHandle);\n            return FMOD_RESULT::FMOD_OK;\n        }\n        return FMOD_RESULT::FMOD_ERR_FILE_NOTFOUND;\n    }\n\n    FMOD_RESULT F_CALL godotFileClose(void* handle, void* userdata) {\n        godot::Ref<godot::FileAccess> file {reinterpret_cast<GodotFileHandle*>(handle)->file};\n        delete reinterpret_cast<GodotFileHandle*>(handle);\n        return FMOD_RESULT::FMOD_OK;\n    }\n\n    FMOD_RESULT F_CALL godotSyncRead(FMOD_ASYNCREADINFO* info, void* userdata) {\n        GodotFileRunner* runner {GodotFileRunner::get_singleton()};\n        int priority {info->priority};\n\n        GodotFileRunner::ReadPriority priorityRank;\n        if (priority >= 50) {\n            priorityRank = GodotFileRunner::ReadPriority::HIGH;\n        } else {\n            priorityRank = GodotFileRunner::ReadPriority::NORMAL;\n        }\n        runner->queueReadRequest(info, priorityRank);\n        return FMOD_RESULT::FMOD_OK;\n    }\n\n    FMOD_RESULT F_CALL godotSyncCancel(FMOD_ASYNCREADINFO* info, void* userdata) {\n        GodotFileRunner* runner {GodotFileRunner::get_singleton()};\n        return runner->cancelReadRequest(info);\n    }\n}// namespace Callbacks"
  },
  {
    "path": "src/callback/file_callbacks.h",
    "content": "#ifndef GODOTFMOD_FILE_CALLBACKS_H\n#define GODOTFMOD_FILE_CALLBACKS_H\n\n#include <fmod_common.h>\n#include <fmod_studio_common.h>\n\n#include <classes/file_access.hpp>\n#include <classes/ref.hpp>\n#include <condition_variable>\n#include <cstring>// This include is required for both Linux and MacOS targets as they don't include the necessary headers for 'memcpy' by default\n#include <thread>\n\nnamespace Callbacks {\n    struct GodotFileHandle {\n        godot::Ref<godot::FileAccess> file;\n    };\n\n    class GodotFileRunner {\n    public:\n        static GodotFileRunner* get_singleton();\n\n        enum ReadPriority {\n            NORMAL,\n            HIGH\n        };\n\n        ~GodotFileRunner() = default;\n\n    private:\n        std::thread fileThread;\n\n        std::condition_variable read_cv;\n        std::mutex read_mut;\n\n        std::condition_variable cancel_cv;\n        std::mutex cancel_mut;\n\n        bool stop = false;\n        FMOD_ASYNCREADINFO* current_request = nullptr;\n        godot::List<FMOD_ASYNCREADINFO*> requests = godot::List<FMOD_ASYNCREADINFO*>();\n\n        GodotFileRunner() = default;\n        GodotFileRunner(const GodotFileRunner&) = delete;\n        GodotFileRunner& operator=(const GodotFileRunner&) = delete;\n\n        void run();\n\n    public:\n        void queueReadRequest(FMOD_ASYNCREADINFO* request, ReadPriority priority);\n        FMOD_RESULT cancelReadRequest(FMOD_ASYNCREADINFO* request);\n        void start();\n        void finish();\n    };\n    FMOD_RESULT F_CALL godotFileOpen(const char* name, unsigned int* filesize, void** handle, void* userdata);\n\n    FMOD_RESULT F_CALL godotFileClose(void* handle, void* userdata);\n\n    FMOD_RESULT F_CALL godotSyncRead(FMOD_ASYNCREADINFO* info, void* userdata);\n\n    FMOD_RESULT F_CALL godotSyncCancel(FMOD_ASYNCREADINFO* info, void* userdata);\n}// namespace Callbacks\n\n#endif// GODOTFMOD_FILE_CALLBACKS_H\n"
  },
  {
    "path": "src/constants.h",
    "content": "#ifndef GODOTFMOD_CONSTANTS_H\n#define GODOTFMOD_CONSTANTS_H\n\nstatic constexpr const char* FMOD_SETTINGS_BASE_PATH = \"Fmod\";\nstatic constexpr const char* FMOD_SETTING_AUTO_INITIALIZE = \"auto_initialize\";\n\nstatic constexpr const bool DEFAULT_AUTO_INITIALIZE = true;\n\n#endif// GODOTFMOD_CONSTANTS_H\n"
  },
  {
    "path": "src/core/fmod_file.cpp",
    "content": "#include \"fmod_file.h\"\n\nusing namespace godot;\n\nvoid FmodFile::_bind_methods() {}"
  },
  {
    "path": "src/core/fmod_file.h",
    "content": "#ifndef GODOTFMOD_FMOD_FILE_H\n#define GODOTFMOD_FMOD_FILE_H\n\n#include \"classes/ref_counted.hpp\"\n#include \"fmod.hpp\"\n\nnamespace godot {\n    class FmodFile : public RefCounted {\n        GDCLASS(FmodFile, RefCounted);\n\n        FMOD::Sound* _wrapped = nullptr;\n\n    public:\n        inline static Ref<FmodFile> create_ref(FMOD::Sound* wrapped) {\n            Ref<FmodFile> ref;\n            if (wrapped) {\n                ref.instantiate();\n                ref->_wrapped = wrapped;\n                wrapped->setUserData(ref.ptr());\n            }\n            return ref;\n        }\n\n        FMOD::Sound* get_wrapped() const { return _wrapped; }\n\n    protected:\n        static void _bind_methods();\n    };\n}// namespace godot\n\n#endif// GODOTFMOD_FMOD_FILE_H\n"
  },
  {
    "path": "src/core/fmod_sound.cpp",
    "content": "#include \"fmod_sound.h\"\n\n#include \"helpers/common.h\"\n\nusing namespace godot;\n\nvoid FmodSound::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"is_valid\"), &FmodSound::is_valid);\n    ClassDB::bind_method(D_METHOD(\"release\"), &FmodSound::release);\n    ClassDB::bind_method(D_METHOD(\"play\"), &FmodSound::play);\n    ClassDB::bind_method(D_METHOD(\"stop\"), &FmodSound::stop);\n    ClassDB::bind_method(D_METHOD(\"set_paused\", \"paused\"), &FmodSound::set_paused);\n    ClassDB::bind_method(D_METHOD(\"is_playing\"), &FmodSound::is_playing);\n    ClassDB::bind_method(D_METHOD(\"set_volume\", \"volume\"), &FmodSound::set_volume);\n    ClassDB::bind_method(D_METHOD(\"get_volume\"), &FmodSound::get_volume);\n    ClassDB::bind_method(D_METHOD(\"set_pitch\", \"pitch\"), &FmodSound::set_pitch);\n    ClassDB::bind_method(D_METHOD(\"get_pitch\"), &FmodSound::get_pitch);\n\n    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, \"pitch\",PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_NONE), \"set_pitch\", \"get_pitch\");\n    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, \"volume\",PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_NONE), \"set_volume\", \"get_volume\");\n}\n\nvoid FmodSound::release() const {\n    ERROR_CHECK(_wrapped->stop());\n}\n\nvoid FmodSound::play() const {\n    set_paused(false);\n}\n\nvoid FmodSound::set_paused(bool paused) const {\n    ERROR_CHECK(_wrapped->setPaused(paused));\n}\n\nvoid FmodSound::stop() const {\n    ERROR_CHECK(_wrapped->stop());\n}\n\nbool FmodSound::is_playing() const {\n    bool isPlaying = false;\n    ERROR_CHECK(_wrapped->isPlaying(&isPlaying));\n    return isPlaying;\n}\n\nvoid FmodSound::set_volume(float volume) const {\n    ERROR_CHECK(_wrapped->setVolume(volume));\n}\n\nfloat FmodSound::get_volume() const {\n    float volume = 0.f;\n    ERROR_CHECK(_wrapped->getVolume(&volume));\n    return volume;\n}\n\nfloat FmodSound::get_pitch() const {\n    float pitch = 0.f;\n    ERROR_CHECK(_wrapped->getPitch(&pitch));\n    return pitch;\n}\n\nvoid FmodSound::set_pitch(float pitch) {\n    ERROR_CHECK(_wrapped->setPitch(pitch));\n}\n\nbool FmodSound::is_valid() const {\n    bool isPlaying;\n    FMOD_RESULT result = _wrapped->isPlaying(&isPlaying);\n    return result != FMOD_ERR_INVALID_HANDLE;\n}"
  },
  {
    "path": "src/core/fmod_sound.h",
    "content": "#ifndef GODOTFMOD_FMOD_SOUND_H\n#define GODOTFMOD_FMOD_SOUND_H\n\n#include \"classes/ref_counted.hpp\"\n#include \"fmod.hpp\"\n\nnamespace godot {\n    class FmodSound : public RefCounted {\n        GDCLASS(FmodSound, RefCounted);\n\n        FMOD::Channel* _wrapped = nullptr;\n\n    public:\n        inline static Ref<FmodSound> create_ref(FMOD::Channel* wrapped) {\n            Ref<FmodSound> ref;\n            if (wrapped) {\n                ref.instantiate();\n                ref->_wrapped = wrapped;\n                wrapped->setUserData(ref.ptr());\n            }\n            return ref;\n        }\n\n        void set_paused(bool paused) const;\n        void stop() const;\n        bool is_playing() const;\n        void set_volume(float volume) const;\n        float get_volume() const;\n        float get_pitch() const;\n        void set_pitch(float pitch);\n\n        bool is_valid() const;\n        void play() const;\n        void release() const;\n\n    protected:\n        static void _bind_methods();\n    };\n}// namespace godot\n\n#endif// GODOTFMOD_FMOD_SOUND_H\n"
  },
  {
    "path": "src/data/performance_data.cpp",
    "content": "#include \"performance_data.h\"\n\nusing namespace godot;\n\nvoid FmodPerformanceData::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"get_dsp\"), &FmodPerformanceData::get_dsp);\n    ClassDB::bind_method(D_METHOD(\"get_geometry\"), &FmodPerformanceData::get_geometry);\n    ClassDB::bind_method(D_METHOD(\"get_stream\"), &FmodPerformanceData::get_stream);\n    ClassDB::bind_method(D_METHOD(\"get_update\"), &FmodPerformanceData::get_update);\n    ClassDB::bind_method(D_METHOD(\"get_convolution1\"), &FmodPerformanceData::get_convolution1);\n    ClassDB::bind_method(D_METHOD(\"get_convolution2\"), &FmodPerformanceData::get_convolution2);\n\n    ClassDB::bind_method(D_METHOD(\"get_studio\"), &FmodPerformanceData::get_studio);\n\n    ClassDB::bind_method(D_METHOD(\"get_currently_allocated\"), &FmodPerformanceData::get_currently_allocated);\n    ClassDB::bind_method(D_METHOD(\"get_max_allocated\"), &FmodPerformanceData::get_max_allocated);\n\n    ClassDB::bind_method(D_METHOD(\"get_sample_bytes_read\"), &FmodPerformanceData::get_sample_bytes_read);\n    ClassDB::bind_method(D_METHOD(\"get_stream_bytes_read\"), &FmodPerformanceData::get_stream_bytes_read);\n    ClassDB::bind_method(D_METHOD(\"get_other_bytes_read\"), &FmodPerformanceData::get_other_bytes_read);\n\n    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, \"dsp\", PropertyHint::PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_READ_ONLY), \"\", \"get_dsp\");\n    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, \"geometry\", PropertyHint::PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_READ_ONLY), \"\", \"get_geometry\");\n    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, \"stream\", PropertyHint::PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_READ_ONLY), \"\", \"get_stream\");\n    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, \"update\", PropertyHint::PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_READ_ONLY), \"\", \"get_update\");\n    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, \"convolution1\", PropertyHint::PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_READ_ONLY), \"\", \"get_convolution1\");\n    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, \"convolution2\", PropertyHint::PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_READ_ONLY), \"\", \"get_convolution2\");\n\n    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, \"studio\", PropertyHint::PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_READ_ONLY), \"\", \"get_studio\");\n\n    ADD_PROPERTY(PropertyInfo(Variant::INT, \"currently_allocated\", PropertyHint::PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_READ_ONLY), \"\", \"get_currently_allocated\");\n    ADD_PROPERTY(PropertyInfo(Variant::INT, \"max_allocated\", PropertyHint::PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_READ_ONLY), \"\", \"get_max_allocated\");\n\n    ADD_PROPERTY(PropertyInfo(Variant::INT, \"sample_bytes_read\", PropertyHint::PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_READ_ONLY), \"\", \"get_sample_bytes_read\");\n    ADD_PROPERTY(PropertyInfo(Variant::INT, \"stream_bytes_read\", PropertyHint::PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_READ_ONLY), \"\", \"get_stream_bytes_read\");\n    ADD_PROPERTY(PropertyInfo(Variant::INT, \"other_bytes_read\", PropertyHint::PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_READ_ONLY), \"\", \"get_other_bytes_read\");\n}\n\nfloat FmodPerformanceData::get_dsp() const {\n    return dsp;\n}\n\nfloat FmodPerformanceData::get_geometry() const {\n    return geometry;\n}\n\nfloat FmodPerformanceData::get_stream() const {\n    return stream;\n}\n\nfloat FmodPerformanceData::get_update() const {\n    return update;\n}\n\nfloat FmodPerformanceData::get_convolution1() const {\n    return convolution1;\n}\n\nfloat FmodPerformanceData::get_convolution2() const {\n    return convolution2;\n}\n\nfloat FmodPerformanceData::get_studio() const {\n    return studio;\n}\n\nint FmodPerformanceData::get_currently_allocated() const {\n    return currently_allocated;\n}\n\nint FmodPerformanceData::get_max_allocated() const {\n    return max_allocated;\n}\n\nint FmodPerformanceData::get_sample_bytes_read() const {\n    return sample_bytes_read;\n}\n\nint FmodPerformanceData::get_stream_bytes_read() const {\n    return stream_bytes_read;\n}\n\nint FmodPerformanceData::get_other_bytes_read() const {\n    return other_bytes_read;\n}\n"
  },
  {
    "path": "src/data/performance_data.h",
    "content": "#ifndef GODOTFMOD_PERFORMANCE_DATA_H\n#define GODOTFMOD_PERFORMANCE_DATA_H\n\n#include \"classes/ref_counted.hpp\"\n\nnamespace godot {\n    class FmodPerformanceData : public RefCounted {\n        GDCLASS(FmodPerformanceData, RefCounted);\n\n    public:\n        float dsp = 0;\n        float geometry = 0;\n        float stream = 0;\n        float update = 0;\n        float convolution1 = 0;\n        float convolution2 = 0;\n        float studio = 0;\n\n        int currently_allocated = 0;\n        int max_allocated = 0;\n\n        int sample_bytes_read = 0;\n        int stream_bytes_read = 0;\n        int other_bytes_read = 0;\n\n        float get_geometry() const;\n        float get_stream() const;\n        float get_update() const;\n        float get_convolution1() const;\n        float get_convolution2() const;\n        float get_studio() const;\n        float get_dsp() const;\n\n        int get_currently_allocated() const;\n        int get_max_allocated() const;\n\n        int get_sample_bytes_read() const;\n        int get_stream_bytes_read() const;\n        int get_other_bytes_read() const;\n\n    protected:\n        static void _bind_methods();\n    };\n\n}// namespace godot\n\n#endif// GODOTFMOD_PERFORMANCE_DATA_H\n"
  },
  {
    "path": "src/fmod_cache.cpp",
    "content": "\n#include \"fmod_cache.h\"\n\n#include \"helpers/common.h\"\n#include \"classes/project_settings.hpp\"\n\nusing namespace godot;\n\nFmodCache::FmodCache(FMOD::Studio::System* p_system, FMOD::System* p_core_system) :\n    system(p_system), core_system(p_core_system) {\n\n}\n\nFmodCache::~FmodCache() {\n    system = nullptr;\n    core_system = nullptr;\n}\n\nvoid FmodCache::update_pending() {\n    if (loading_banks.size() == 0) { return; }\n    List<Ref<FmodBank>> to_delete;\n    for (const Ref<FmodBank>& loading_bank : loading_banks) {\n        int loading_state = loading_bank->get_loading_state();\n        if (loading_state == FMOD_STUDIO_LOADING_STATE_LOADED) {\n            _get_bank_data(loading_bank);\n            banks[loading_bank->get_godot_res_path()] = loading_bank.ptr();\n            to_delete.push_back(loading_bank);\n        } else if (loading_state == FMOD_STUDIO_LOADING_STATE_ERROR) {\n            to_delete.push_back(loading_bank);\n            GODOT_LOG_ERROR(\"Fmod Sound System: Error loading bank.\")\n        }\n    }\n    for (const Ref<FmodBank>& element : to_delete) {\n        loading_banks.erase(element);\n    }\n}\n\nvoid FmodCache::force_loading() {\n    while (is_loading()) {\n        update_pending();\n    }\n}\n\nbool FmodCache::is_loading() {\n    return loading_banks.size() > 0;\n}\n\nRef<FmodBank> FmodCache::add_bank(const String& bank_path, unsigned int flag) {\n    FMOD::Studio::Bank* bank = nullptr;\n    ERROR_CHECK_WITH_REASON(system->loadBankFile(bank_path.utf8().get_data(), flag, &bank), vformat(\"Cannot load bank %s\", bank_path));\n    if (!bank) { return {}; }\n    Ref<FmodBank> ref = FmodBank::create_ref(bank, bank_path);\n    GODOT_LOG_VERBOSE(\"FMOD Sound System: LOADING BANK \" + String(bank_path))\n    loading_banks.push_back(ref);\n    if (flag != FMOD_STUDIO_LOAD_BANK_NONBLOCKING) { force_loading(); }\n    return ref;\n}\n\nvoid FmodCache::remove_bank(const String& bank_path) {\n    if (!banks.has(bank_path)) {\n        GODOT_LOG_ERROR(vformat(\"Cannot unload bank with path %s, not in cache.\", bank_path));\n        return;\n    }\n    FmodBank* bank = banks[bank_path];\n    _remove_bank_data(bank);\n    ERROR_CHECK_WITH_REASON(bank->get_wrapped()->unload(), vformat(\"Cannot unload bank %s\", bank_path));\n    banks.erase(bank_path);\n}\n\nbool FmodCache::has_bank(const String& bankPath) {\n    return banks.has(bankPath);\n}\n\nRef<FmodBank> FmodCache::get_bank(const String& bankPath) {\n    return banks.get(bankPath);\n}\n\n#ifndef IOS_ENABLED\n\nuint32_t FmodCache::add_plugin(const String& p_plugin_path, uint32_t p_priority) {\n    uint32_t handle;\n#if defined(ANDROID_ENABLED) && !defined(TOOLS_ENABLED)\n    const char* plugin_path = p_plugin_path.utf8().get_data();\n#else\n    const char* plugin_path = ProjectSettings::get_singleton()->globalize_path(p_plugin_path).utf8().get_data();\n#endif\n    ERROR_CHECK(core_system->loadPlugin(plugin_path, &handle, p_priority));\n    plugin_handles.append(handle);\n    return handle;\n}\n\n#else\n\nvoid FmodCache::add_plugin(uint32_t p_plugin_handle) {\n    plugin_handles.append(p_plugin_handle);\n}\n\n#endif\n\nbool FmodCache::has_plugin(uint32_t p_plugin_handle) const {\n    return plugin_handles.has(p_plugin_handle);\n}\n\nvoid FmodCache::remove_plugin(uint32_t p_plugin_handle) {\n    if (!has_plugin(p_plugin_handle)) {\n        GODOT_LOG_ERROR(vformat(\"Cannot unload plugin with handle %s, not in cache.\", p_plugin_handle));\n        return;\n    }\n\n    ERROR_CHECK(core_system->unloadPlugin(p_plugin_handle));\n    plugin_handles.erase(p_plugin_handle);\n}\n\n\nRef<FmodFile> FmodCache::add_file(const String& file_path, unsigned int flag) {\n    FMOD::System* core = nullptr;\n    ERROR_CHECK(system->getCoreSystem(&core));\n\n    FMOD::Sound* sound = nullptr;\n    ERROR_CHECK_WITH_REASON(core->createSound(file_path.utf8().get_data(), flag, nullptr, &sound), vformat(\"Cannot create sound %s\", file_path));\n    if (sound) {\n        Ref<FmodFile> ref = FmodFile::create_ref(sound);\n        files[file_path] = ref;\n        GODOT_LOG_VERBOSE(\"FMOD Sound System: LOADING AS SOUND FILE\" + String(file_path))\n        return ref;\n    }\n    return {};\n}\n\nbool FmodCache::has_file(const String& filePath) {\n    return files.find(filePath).operator bool();\n}\n\nRef<FmodFile> FmodCache::get_file(const String& filePath) {\n    return files.get(filePath);\n}\n\nvoid FmodCache::remove_file(const String& filePath) {\n    if (files.has(filePath)) {\n        Ref<FmodFile> ref = files[filePath];\n        ERROR_CHECK(ref->get_wrapped()->release());\n        files.erase(filePath);\n    }\n}\n\nbool FmodCache::has_vca_guid(const FMOD_GUID& guid) {\n    return vcas.has(guid);\n}\n\nbool FmodCache::has_vca_path(const String& vcaPath) {\n    return strings_to_guid.has(vcaPath) && vcas.has(strings_to_guid.get(vcaPath));\n}\n\nbool FmodCache::has_bus_guid(const FMOD_GUID& guid) {\n    return buses.has(guid);\n}\n\nbool FmodCache::has_bus_path(const String& busPath) {\n    return strings_to_guid.has(busPath) && buses.has(strings_to_guid.get(busPath));\n}\n\nbool FmodCache::has_event_guid(const FMOD_GUID& guid) {\n    return event_descriptions.has(guid);\n}\n\nbool FmodCache::has_event_path(const String& eventPath) {\n    return strings_to_guid.has(eventPath) && event_descriptions.has(strings_to_guid.get(eventPath));\n}\n\nRef<FmodVCA> FmodCache::get_vca(const FMOD_GUID& guid) {\n    if (\n            HashMap<FMOD_GUID, Ref<FmodVCA>, FmodGuidHashMapHasher, FmodGuidHashMapComparer>::Iterator iterator{\n                    vcas.find(guid)\n            }\n    ) {\n        return iterator->value;\n    }\n\n#ifdef DEBUG_ENABLED\n    GODOT_LOG_WARNING(vformat(\"Cannot find vca with guid: %s\", fmod_guid_to_string(guid)));\n#endif\n\n    return {};\n}\n\nRef<FmodVCA> FmodCache::get_vca(const String& vca_path) {\n    if (HashMap<String, FMOD_GUID>::Iterator iterator {strings_to_guid.find(vca_path)}) {\n        return get_vca(iterator->value);\n    }\n\n#ifdef DEBUG_ENABLED\n    GODOT_LOG_WARNING(vformat(\"Cannot find vca with path: %s\", vca_path));\n#endif\n\n    return {};\n}\n\nRef<FmodBus> FmodCache::get_bus(const FMOD_GUID& guid) {\n    if (\n            HashMap<FMOD_GUID, Ref<FmodBus>, FmodGuidHashMapHasher, FmodGuidHashMapComparer>::Iterator iterator{\n                    buses.find(guid)\n            }\n    ) {\n        return iterator->value;\n    }\n\n#ifdef DEBUG_ENABLED\n    GODOT_LOG_WARNING(vformat(\"Cannot find bus with guid: %s\", fmod_guid_to_string(guid)));\n#endif\n\n    return {};\n}\n\nRef<FmodBus> FmodCache::get_bus(const String& bus_path) {\n    if (HashMap<String, FMOD_GUID>::Iterator iterator {strings_to_guid.find(bus_path)}) {\n        return get_bus(iterator->value);\n    }\n\n#ifdef DEBUG_ENABLED\n    GODOT_LOG_WARNING(vformat(\"Cannot find bus with path: %s\", bus_path));\n#endif\n\n    return {};\n}\n\nRef<FmodEventDescription> FmodCache::get_event(const FMOD_GUID& guid) {\n    if (\n      HashMap<FMOD_GUID, Ref<FmodEventDescription>, FmodGuidHashMapHasher, FmodGuidHashMapComparer>::Iterator iterator {\n          event_descriptions.find(guid)\n      }\n    ) {\n        return iterator->value;\n    }\n\n#ifdef DEBUG_ENABLED\n    GODOT_LOG_WARNING(vformat(\"Cannot find event with guid: %s\", fmod_guid_to_string(guid)));\n#endif\n\n    return {};\n}\n\nRef<FmodEventDescription> FmodCache::get_event(const String& eventPath) {\n    if (HashMap<String, FMOD_GUID>::Iterator iterator {strings_to_guid.find(eventPath)}) {\n        return get_event(iterator->value);\n    }\n\n#ifdef DEBUG_ENABLED\n    GODOT_LOG_WARNING(vformat(\"Cannot find event with path: %s\", eventPath));\n#endif\n\n    return {};\n}\n\nFMOD_GUID FmodCache::get_event_guid(const String& event_path) {\n    if (HashMap<String, FMOD_GUID>::Iterator iterator {strings_to_guid.find(event_path)}) {\n        return iterator->value;\n    }\n\n    return {};\n}\n\nString FmodCache::get_event_path(const FMOD_GUID& guid) {\n    if (\n            HashMap<FMOD_GUID, Ref<FmodEventDescription>, FmodGuidHashMapHasher, FmodGuidHashMapComparer>::Iterator iterator{\n                    event_descriptions.find(guid)\n            }\n            ) {\n        return iterator->value->get_path();\n    }\n\n    return {};\n}\n\nbool FmodCache::is_master_loaded() {\n    return banks.size() > 0;\n}\n\nvoid FmodCache::clear() {\n    force_loading();\n    event_descriptions.clear();\n    buses.clear();\n    vcas.clear();\n    banks.clear();\n}\n\nvoid FmodCache::_get_bank_data(Ref<FmodBank> bank) {\n    bank->update_bank_data();\n    for (Ref<FmodBus> bus : bank->get_buses()) {\n        FMOD_GUID guid {bus->get_guid()};\n        buses[guid] = bus;\n        strings_to_guid[bus->get_path()] = guid;\n    }\n    for (Ref<FmodVCA> vca : bank->get_vcas()) {\n        FMOD_GUID guid {vca->get_guid()};\n        vcas[guid] = vca;\n        strings_to_guid[vca->get_path()] = guid;\n    }\n    for (Ref<FmodEventDescription> desc : bank->get_event_descriptions()) {\n        FMOD_GUID guid {desc->get_guid()};\n        event_descriptions[guid] = desc;\n        strings_to_guid[desc->get_path()] = guid;\n    }\n}\n\nvoid FmodCache::_remove_bank_data(FmodBank* bank) {\n    for (Ref<FmodBus> bus : bank->get_buses()) {\n        strings_to_guid.erase(bus->get_path());\n        buses.erase(bus->get_guid());\n    }\n    for (Ref<FmodVCA> vca : bank->get_vcas()) {\n        strings_to_guid.erase(vca->get_path());\n        vcas.erase(vca->get_guid());\n    }\n    for (Ref<FmodEventDescription> desc : bank->get_event_descriptions()) {\n        strings_to_guid.erase(desc->get_path());\n        event_descriptions.erase(desc->get_guid());\n    }\n}"
  },
  {
    "path": "src/fmod_cache.h",
    "content": "#ifndef GODOTFMOD_FMOD_CACHE_H\n#define GODOTFMOD_FMOD_CACHE_H\n\n#include \"core/fmod_file.h\"\n#include \"fmod_studio.hpp\"\n#include \"studio/fmod_bank.h\"\n#include \"studio/fmod_bus.h\"\n#include \"studio/fmod_event_description.h\"\n#include \"studio/fmod_vca.h\"\n#include \"templates/hash_map.hpp\"\n\nnamespace godot {\n    class FmodServer;\n\n    struct FmodGuidHashMapHasher {\n        static _FORCE_INLINE_ uint32_t hash(const FMOD_GUID& guid) {\n            return guid.Data1;\n        }\n    };\n\n    struct FmodGuidHashMapComparer {\n        static bool compare(const FMOD_GUID& p_lhs, const FMOD_GUID& p_rhs) {\n            uint64_t* left_guid {reinterpret_cast<uint64_t*>(const_cast<FMOD_GUID*>(&p_lhs))};\n            uint64_t* right_guid {reinterpret_cast<uint64_t*>(const_cast<FMOD_GUID*>(&p_rhs))};\n            return left_guid[0] == right_guid[0] && left_guid[1] == right_guid[1];\n        }\n    };\n\n    class FmodCache {\n        friend class FmodServer;\n\n        FMOD::Studio::System* system;\n        FMOD::System* core_system;\n\n        List<Ref<FmodBank>> loading_banks;\n\n        HashMap<String, Ref<FmodFile>> files;\n        HashMap<String, FmodBank*> banks;\n        Vector<uint32_t> plugin_handles;\n\n        HashMap<FMOD_GUID, Ref<FmodEventDescription>, FmodGuidHashMapHasher, FmodGuidHashMapComparer> event_descriptions;\n        HashMap<FMOD_GUID, Ref<FmodBus>, FmodGuidHashMapHasher, FmodGuidHashMapComparer> buses;\n        HashMap<FMOD_GUID, Ref<FmodVCA>, FmodGuidHashMapHasher, FmodGuidHashMapComparer> vcas;\n\n        HashMap<String, FMOD_GUID> strings_to_guid;\n\n        void _get_bank_data(Ref<FmodBank> bank);\n        void _remove_bank_data(FmodBank* bank);\n\n    public:\n        FmodCache() = delete;\n        FmodCache(const FmodCache& other) = delete;\n        FmodCache(FMOD::Studio::System* p_system, FMOD::System* p_core_system);\n\n        ~FmodCache();\n\n        Ref<FmodBank> add_bank(const String& bank_path, unsigned int flag);\n        bool has_bank(const String& bankPath);\n        Ref<FmodBank> get_bank(const String& bankPath);\n        void remove_bank(const String& bank_path);\n\n#ifndef IOS_ENABLED\n        uint32_t add_plugin(const String& p_plugin_path, uint32_t p_priority = 0);\n#else\n        void add_plugin(uint32_t p_plugin_handle);\n#endif\n        bool has_plugin(uint32_t p_plugin_handle) const;\n        void remove_plugin(uint32_t p_plugin_handle);\n\n        Ref<FmodFile> add_file(const String& filePath, unsigned int flag);\n        bool has_file(const String& filePath);\n        Ref<FmodFile> get_file(const String& filePath);\n        void remove_file(const String& filePath);\n\n        bool is_master_loaded();\n        void clear();\n\n        void update_pending();\n        void force_loading();\n        bool is_loading();\n\n        bool has_vca_guid(const FMOD_GUID& guid);\n        bool has_vca_path(const String& vcaPath);\n        bool has_bus_guid(const FMOD_GUID& guid);\n        bool has_bus_path(const String& busPath);\n        bool has_event_guid(const FMOD_GUID& guid);\n        bool has_event_path(const String& eventPath);\n\n        Ref<FmodVCA> get_vca(const FMOD_GUID& guid);\n        Ref<FmodVCA> get_vca(const String& vca_path);\n        Ref<FmodBus> get_bus(const FMOD_GUID& guid);\n        Ref<FmodBus> get_bus(const String& bus_path);\n        Ref<FmodEventDescription> get_event(const FMOD_GUID& guid);\n        Ref<FmodEventDescription> get_event(const String& eventPath);\n        FMOD_GUID get_event_guid(const String& event_path);\n        String get_event_path(const FMOD_GUID& guid);\n    };\n}// namespace godot\n\n#endif// GODOTFMOD_FMOD_CACHE_H\n"
  },
  {
    "path": "src/fmod_logging.cpp",
    "content": "#include \"fmod_logging.h\"\n\n#include <classes/project_settings.hpp>\n#include <fmod.hpp>\n#include <sstream>\n#include <variant/string.hpp>\n#include <variant/utility_functions.hpp>\n#include <resources/fmod_logging_settings.h>\n\nnamespace godot {\n\n    // initialize FMOD logging based on project settings\n    void logging_init() {\n        const Ref<FmodLoggingSettings> p_logging_settings = FmodLoggingSettings::get_from_project_settings();\n\n        if (p_logging_settings.is_valid()) {\n            unsigned int debug_flags = p_logging_settings->_debug_level_to_fmod();\n            FMOD_DEBUG_MODE log_output = static_cast<FMOD_DEBUG_MODE>(p_logging_settings->get_log_output());\n\n            switch (log_output) {\n                case FMOD_DEBUG_MODE_TTY: {\n                    // Output to terminal/console\n                    FMOD::Debug_Initialize(debug_flags, FMOD_DEBUG_MODE_TTY, nullptr, nullptr);\n                    break;\n                }\n\n                case FMOD_DEBUG_MODE_CALLBACK: {\n                    // Output to a callback -> GODOT\n                    FMOD::Debug_Initialize(debug_flags, FMOD_DEBUG_MODE_CALLBACK, fmod_debug_callback, nullptr);\n                    break;\n                }\n\n                case FMOD_DEBUG_MODE_FILE: {\n                    UtilityFunctions::push_warning(\"FMOD log output set to File\");\n                    // Output to a file\n                    String file_path = p_logging_settings->get_log_file_path();\n                    CharString file_path_utf8 = file_path.utf8();\n\n                    file_path = ProjectSettings::get_singleton()->globalize_path(file_path);\n                    file_path_utf8 = file_path.utf8();\n\n                    FMOD::Debug_Initialize(debug_flags, FMOD_DEBUG_MODE_FILE, nullptr, file_path_utf8);\n                    break;\n                }\n\n                default: {\n                    // Fallback to TTY if somehow an invalid value is set\n                    FMOD::Debug_Initialize(debug_flags, FMOD_DEBUG_MODE_TTY, nullptr, nullptr);\n                    UtilityFunctions::push_warning(\"Invalid FMOD log output setting, defaulting to TTY\");\n                    break;\n                }\n            }\n        }\n    }\n\n    // Direct logging function implementation\n    void log_fmod_message(FMODLogLevel level, const String& message) {\n        switch (level) {\n            case LOG_ERROR:\n                UtilityFunctions::push_error(message);\n                break;\n            case LOG_WARNING:\n                UtilityFunctions::push_warning(message);\n                break;\n            case LOG_VERBOSE:\n                UtilityFunctions::print_verbose(message);\n                break;\n            case LOG_INFO:\n            default:\n                UtilityFunctions::print(message);\n                break;\n        }\n    }\n\n    extern \"C\" {\n    FMOD_RESULT fmod_debug_callback(FMOD_DEBUG_FLAGS flags, const char* file, int line, const char* func, const char* message) {\n        if (!message) { return FMOD_OK; }\n\n        String debug_message;\n\n        // Determine the log level and prefix\n        FMODLogLevel log_level = LOG_INFO;\n        if (flags & FMOD_DEBUG_LEVEL_ERROR) {\n            debug_message += \"[FMOD ERROR]\";\n            log_level = LOG_ERROR;\n        } else if (flags & FMOD_DEBUG_LEVEL_WARNING) {\n            debug_message += \"[FMOD WARN]\";\n            log_level = LOG_WARNING;\n        } else if (flags & FMOD_DEBUG_LEVEL_LOG) {\n            debug_message += \"[FMOD INFO]\";\n        } else {\n            debug_message += \"[FMOD]\";\n        }\n\n        // Add type information if available\n        if (flags & FMOD_DEBUG_TYPE_MEMORY) { debug_message += \"[MEM]\"; }\n        if (flags & FMOD_DEBUG_TYPE_FILE) { debug_message += \"[FILE]\"; }\n        if (flags & FMOD_DEBUG_TYPE_CODEC) { debug_message += \"[CODEC]\"; }\n        if (flags & FMOD_DEBUG_TYPE_TRACE) { debug_message += \"[TRACE]\"; }\n\n        // Format the message based on display flags\n        if ((flags & FMOD_DEBUG_DISPLAY_LINENUMBERS) && file && func) {\n            debug_message += String(\" \") + file + \":\" + String::num_int64(line) + \" in \" + func + \"(): \" + message;\n        } else if (file && func) {\n            debug_message += String(\" \") + file + \" in \" + func + \"(): \" + message;\n        } else if (func) {\n            debug_message += String(\" \") + func + \"(): \" + message;\n        } else {\n            debug_message += String(\" \") + message;\n        }\n\n        debug_message.strip_edges();\n\n        log_fmod_message(log_level, debug_message);\n        return FMOD_OK;\n    }\n    }\n\n}// namespace godot\n"
  },
  {
    "path": "src/fmod_logging.h",
    "content": "#ifndef GODOTFMOD_FMOD_LOGGING_H\n#define GODOTFMOD_FMOD_LOGGING_H\n\n#include \"fmod_common.h\"\n\n#include <sstream>\n#include <variant/string.hpp>\n\nnamespace godot {\n\n    // Log level enumeration\n    enum FMODLogLevel {\n        LOG_INFO,\n        LOG_WARNING,\n        LOG_ERROR,\n        LOG_VERBOSE// Added for verbose logging\n    };\n\n    void logging_init();\n\n    // Core logging function\n    void log_fmod_message(FMODLogLevel level, const String& message);\n\n    // FMOD debug callback function\n    extern \"C\" {\n    FMOD_RESULT fmod_debug_callback(FMOD_DEBUG_FLAGS flags, const char* file, int line, const char* func, const char* message);\n    }\n\n}// namespace godot\n\n#endif\n"
  },
  {
    "path": "src/fmod_server.cpp",
    "content": "#include \"classes/dir_access.hpp\"\n#include \"classes/engine.hpp\"\n#include \"classes/os.hpp\"\n#include \"core/fmod_sound.h\"\n#include \"data/performance_data.h\"\n#include \"fmod_logging.h\"\n#include \"helpers/common.h\"\n#include \"helpers/maths.h\"\n#include \"plugins/ios_plugins_loader.h\"\n#include \"plugins/plugins_helper.h\"\n\n#include <fmod_server.h>\n\n#include <classes/node3d.hpp>\n#include <classes/project_settings.hpp>\n\nusing namespace godot;\n\nFmodServer* FmodServer::singleton = nullptr;\n\nvoid FmodServer::_bind_methods() {\n    // LIFECYCLE\n    ClassDB::bind_method(D_METHOD(\"init\", \"p_settings\"), &FmodServer::init);\n    ClassDB::bind_method(D_METHOD(\"update\"), &FmodServer::update);\n    ClassDB::bind_method(D_METHOD(\"shutdown\"), &FmodServer::shutdown);\n\n    // SETTINGS\n    ClassDB::bind_method(D_METHOD(\"set_software_format\", \"p_settings\"), &FmodServer::set_software_format);\n    ClassDB::bind_method(D_METHOD(\"set_sound_3D_settings\", \"p_settings\"), &FmodServer::set_sound_3d_settings);\n    ClassDB::bind_method(D_METHOD(\"set_system_dsp_buffer_size\", \"dsp_settings\"), &FmodServer::set_system_dsp_buffer_size);\n    ClassDB::bind_method(D_METHOD(\"get_system_dsp_buffer_settings\"), &FmodServer::get_system_dsp_buffer_settings);\n    ClassDB::bind_method(D_METHOD(\"get_system_dsp_buffer_length\"), &FmodServer::get_system_dsp_buffer_length);\n    ClassDB::bind_method(D_METHOD(\"get_system_dsp_num_buffers\"), &FmodServer::get_system_dsp_num_buffers);\n\n    // OBJECT\n    ClassDB::bind_method(D_METHOD(\"check_vca_guid\", \"guid\"), &FmodServer::check_vca_guid);\n    ClassDB::bind_method(D_METHOD(\"check_vca_path\", \"cvaPath\"), &FmodServer::check_vca_path);\n    ClassDB::bind_method(D_METHOD(\"check_bus_guid\", \"guid\"), &FmodServer::check_bus_guid);\n    ClassDB::bind_method(D_METHOD(\"check_bus_path\", \"busPath\"), &FmodServer::check_bus_path);\n    ClassDB::bind_method(D_METHOD(\"check_event_guid\", \"guid\"), &FmodServer::check_event_guid);\n    ClassDB::bind_method(D_METHOD(\"check_event_path\", \"eventPath\"), &FmodServer::check_event_path);\n    ClassDB::bind_method(D_METHOD(\"get_vca_from_guid\", \"guid\"), &FmodServer::get_vca_from_guid);\n    ClassDB::bind_method(D_METHOD(\"get_vca\", \"cvaPath\"), &FmodServer::get_vca);\n    ClassDB::bind_method(D_METHOD(\"get_bus_from_guid\", \"guid\"), &FmodServer::get_bus_from_guid);\n    ClassDB::bind_method(D_METHOD(\"get_bus\", \"busPath\"), &FmodServer::get_bus);\n    ClassDB::bind_method(D_METHOD(\"get_event_from_guid\", \"guid\"), &FmodServer::get_event_from_guid);\n    ClassDB::bind_method(D_METHOD(\"get_event\", \"eventPath\"), &FmodServer::get_event);\n    ClassDB::bind_method(D_METHOD(\"get_event_guid\", \"event_path\"), &FmodServer::get_event_guid);\n    ClassDB::bind_method(D_METHOD(\"get_event_path\", \"guid\"), &FmodServer::get_event_path);\n    ClassDB::bind_method(D_METHOD(\"get_all_vca\"), &FmodServer::get_all_vca);\n    ClassDB::bind_method(D_METHOD(\"get_all_buses\"), &FmodServer::get_all_buses);\n    ClassDB::bind_method(D_METHOD(\"get_all_event_descriptions\"), &FmodServer::get_all_event_descriptions);\n    ClassDB::bind_method(D_METHOD(\"get_all_banks\"), &FmodServer::get_all_banks);\n\n    // DEBUGGING\n    ClassDB::bind_method(D_METHOD(\"get_available_drivers\"), &FmodServer::get_available_drivers);\n    ClassDB::bind_method(D_METHOD(\"get_driver\"), &FmodServer::get_driver);\n    ClassDB::bind_method(D_METHOD(\"set_driver\", \"id\"), &FmodServer::set_driver);\n    ClassDB::bind_method(D_METHOD(\"get_performance_data\"), &FmodServer::get_performance_data);\n\n    // GLOBAL PARAMETERS\n    ClassDB::bind_method(D_METHOD(\"set_global_parameter_by_name\", \"parameter_name\", \"value\"), &FmodServer::set_global_parameter_by_name);\n    ClassDB::bind_method(D_METHOD(\"set_global_parameter_by_name_with_label\", \"parameter_name\", \"label\"), &FmodServer::set_global_parameter_by_name_with_label);\n    ClassDB::bind_method(D_METHOD(\"get_global_parameter_by_name\", \"parameter_name\"), &FmodServer::get_global_parameter_by_name);\n    ClassDB::bind_method(D_METHOD(\"set_global_parameter_by_id\", \"parameter_id\", \"value\"), &FmodServer::set_global_parameter_by_id);\n    ClassDB::bind_method(D_METHOD(\"set_global_parameter_by_id_with_label\", \"parameter_id\", \"label\"), &FmodServer::set_global_parameter_by_id_with_label);\n    ClassDB::bind_method(D_METHOD(\"get_global_parameter_by_id\", \"parameter_id\"), &FmodServer::get_global_parameter_by_id);\n    ClassDB::bind_method(D_METHOD(\"get_global_parameter_desc_by_name\", \"parameterName\"), &FmodServer::get_global_parameter_desc_by_name);\n    ClassDB::bind_method(D_METHOD(\"get_global_parameter_desc_by_id\", \"parameter_id\"), &FmodServer::get_global_parameter_desc_by_id);\n    ClassDB::bind_method(D_METHOD(\"get_global_parameter_desc_count\"), &FmodServer::get_global_parameter_desc_count);\n    ClassDB::bind_method(D_METHOD(\"get_global_parameter_desc_list\"), &FmodServer::get_global_parameter_desc_list);\n\n    // LISTENERS\n    ClassDB::bind_method(D_METHOD(\"add_listener\", \"index\", \"game_obj\"), &FmodServer::add_listener);\n    ClassDB::bind_method(D_METHOD(\"remove_listener\", \"index\", \"game_obj\"), &FmodServer::remove_listener);\n    ClassDB::bind_method(D_METHOD(\"set_listener_number\", \"listenerNumber\"), &FmodServer::set_system_listener_number);\n    ClassDB::bind_method(D_METHOD(\"get_listener_number\"), &FmodServer::get_system_listener_number);\n    ClassDB::bind_method(D_METHOD(\"get_listener_weight\", \"index\"), &FmodServer::get_system_listener_weight);\n    ClassDB::bind_method(D_METHOD(\"set_listener_weight\", \"index\", \"weight\"), &FmodServer::set_system_listener_weight);\n    ClassDB::bind_method(D_METHOD(\"get_listener_transform3d\", \"index\"), &FmodServer::get_listener_transform3d);\n    ClassDB::bind_method(D_METHOD(\"get_listener_transform2d\", \"index\"), &FmodServer::get_listener_transform2d);\n    ClassDB::bind_method(D_METHOD(\"get_listener_3d_velocity\", \"index\"), &FmodServer::get_listener_3d_velocity);\n    ClassDB::bind_method(D_METHOD(\"get_listener_2d_velocity\", \"index\"), &FmodServer::get_listener_2d_velocity);\n    ClassDB::bind_method(D_METHOD(\"set_listener_transform3d\", \"index\", \"transform\"), &FmodServer::set_listener_transform3d);\n    ClassDB::bind_method(D_METHOD(\"set_listener_transform2d\", \"index\", \"transform\"), &FmodServer::set_listener_transform2d);\n    ClassDB::bind_method(D_METHOD(\"set_listener_lock\", \"index\", \"isLocked\"), &FmodServer::set_listener_lock);\n    ClassDB::bind_method(D_METHOD(\"get_listener_lock\", \"index\"), &FmodServer::get_listener_lock);\n    ClassDB::bind_method(D_METHOD(\"get_object_attached_to_listener\", \"index\"), &FmodServer::get_object_attached_to_listener);\n\n    // BANKS\n    ClassDB::bind_method(D_METHOD(\"load_bank\", \"pathToBank\", \"flag\"), &FmodServer::load_bank);\n    ClassDB::bind_method(D_METHOD(\"wait_for_all_loads\"), &FmodServer::wait_for_all_loads);\n    ClassDB::bind_method(D_METHOD(\"banks_still_loading\"), &FmodServer::banks_still_loading);\n\n    // PLUGINS\n    ClassDB::bind_method(D_METHOD(\"load_plugin\", \"p_plugin_path\", \"p_priority\"), &FmodServer::load_plugin, DEFVAL(0));\n    ClassDB::bind_method(D_METHOD(\"unload_plugin\", \"p_plugin_handle\"), &FmodServer::unload_plugin);\n    ClassDB::bind_method(D_METHOD(\"is_plugin_loaded\", \"p_plugin_handle\"), &FmodServer::is_plugin_loaded);\n\n    ClassDB::bind_method(D_METHOD(\"load_file_as_sound\", \"path\"), &FmodServer::load_file_as_sound);\n    ClassDB::bind_method(D_METHOD(\"load_file_as_music\", \"path\"), &FmodServer::load_file_as_music);\n    ClassDB::bind_method(D_METHOD(\"unload_file\", \"path\"), &FmodServer::unload_file);\n\n    ClassDB::bind_method(D_METHOD(\"create_event_instance_with_guid\", \"guid\"), &FmodServer::create_event_instance_with_guid);\n    ClassDB::bind_method(D_METHOD(\"create_event_instance\", \"eventPath\"), &FmodServer::create_event_instance);\n    ClassDB::bind_method(D_METHOD(\"create_event_instance_from_description\", \"eventPath\"), &FmodServer::create_event_instance_from_description);\n    ClassDB::bind_method(D_METHOD(\"play_one_shot_using_guid\", \"guid\"), &FmodServer::play_one_shot_using_guid);\n    ClassDB::bind_method(D_METHOD(\"play_one_shot\", \"event_name\"), &FmodServer::play_one_shot);\n    ClassDB::bind_method(D_METHOD(\"play_one_shot_using_event_description\", \"event_description\"), &FmodServer::play_one_shot_using_event_description);\n    ClassDB::bind_method(D_METHOD(\"play_one_shot_using_guid_with_params\", \"guid\", \"parameters\"), &FmodServer::play_one_shot_using_guid_with_params);\n    ClassDB::bind_method(D_METHOD(\"play_one_shot_with_params\", \"event_name\", \"parameters\"), &FmodServer::play_one_shot_with_params);\n    ClassDB::bind_method(\n      D_METHOD(\"play_one_shot_using_event_description_with_params\", \"event_description\", \"parameters\"),\n      &FmodServer::play_one_shot_using_event_description_with_params\n    );\n    ClassDB::bind_method(D_METHOD(\"play_one_shot_using_guid_attached\", \"guid\", \"game_obj\"), &FmodServer::play_one_shot_using_guid_attached);\n    ClassDB::bind_method(D_METHOD(\"play_one_shot_attached\", \"event_name\", \"game_obj\"), &FmodServer::play_one_shot_attached);\n    ClassDB::bind_method(\n      D_METHOD(\"play_one_shot_using_event_description_attached\", \"event_description\", \"game_obj\"),\n      &FmodServer::play_one_shot_using_event_description_attached\n    );\n    ClassDB::bind_method(\n      D_METHOD(\"play_one_shot_using_guid_attached_with_params\", \"guid\", \"game_obj\", \"parameters\"),\n      &FmodServer::play_one_shot_using_guid_attached_with_params\n    );\n    ClassDB::bind_method(D_METHOD(\"play_one_shot_attached_with_params\", \"event_name\", \"game_obj\", \"parameters\"), &FmodServer::play_one_shot_attached_with_params);\n    ClassDB::bind_method(\n      D_METHOD(\"play_one_shot_using_event_description_attached_with_params\", \"event_description\", \"game_obj\", \"parameters\"),\n      &FmodServer::play_one_shot_using_event_description_attached_with_params\n    );\n    ClassDB::bind_method(D_METHOD(\"pause_all_events\"), &FmodServer::pause_all_events);\n    ClassDB::bind_method(D_METHOD(\"unpause_all_events\"), &FmodServer::unpause_all_events);\n    ClassDB::bind_method(D_METHOD(\"mute_all_events\"), &FmodServer::mute_all_events);\n    ClassDB::bind_method(D_METHOD(\"unmute_all_events\"), &FmodServer::unmute_all_events);\n    ClassDB::bind_method(D_METHOD(\"mixer_suspend\"), &FmodServer::mixer_suspend);\n    ClassDB::bind_method(D_METHOD(\"mixer_resume\"), &FmodServer::mixer_resume);\n\n    ClassDB::bind_method(D_METHOD(\"create_sound_instance\", \"path\"), &FmodServer::create_sound_instance);\n    REGISTER_ALL_CONSTANTS\n}\n\nFmodServer::FmodServer() :\n  system(nullptr),\n  coreSystem(nullptr),\n  isInitialized(false),\n  isNotInitializedPrinted(false),\n  distanceScale(1.0),\n  cache(nullptr) {\n    ERR_FAIL_COND(singleton != nullptr);\n    singleton = this;\n    callback_mutex.instantiate();\n    performanceData = create_ref<FmodPerformanceData>();\n    Callbacks::GodotFileRunner::get_singleton()->start();\n}\n\nFmodServer::~FmodServer() {\n    callbacks_to_process.clear();\n\n    ERR_FAIL_COND(singleton != this);\n    singleton = nullptr;\n}\n\nFmodServer* FmodServer::get_singleton() {\n    return singleton;\n}\n\nvoid FmodServer::init(const Ref<FmodGeneralSettings>& p_settings) {\n    if (isInitialized) {\n        GODOT_LOG_WARNING(\"Fmod system already initialized.\")\n        return;\n    }\n\n    logging_init();\n\n    // initialize FMOD Studio and FMOD Core System with provided flags\n    if (system == nullptr && coreSystem == nullptr) {\n        ERROR_CHECK(FMOD::Studio::System::create(&system));\n        ERROR_CHECK(system->getCoreSystem(&coreSystem));\n    }\n\n    // editing advanced settings to set random seed before system initialization\n    FMOD_ADVANCEDSETTINGS advancedSettings = {};\n    advancedSettings.cbSize = sizeof(FMOD_ADVANCEDSETTINGS);\n    ERROR_CHECK(coreSystem->getAdvancedSettings(&advancedSettings));\n\n    advancedSettings.randomSeed = static_cast<unsigned int>(std::time(nullptr));// Use time as a seed\n    ERROR_CHECK(coreSystem->setAdvancedSettings(&advancedSettings));\n\n    FMOD_STUDIO_INITFLAGS studio_init_flags = FMOD_STUDIO_INIT_NORMAL;\n\n    if (\n#ifdef TOOLS_ENABLED\n            !Engine::get_singleton()->is_editor_hint() &&\n#endif\n            p_settings->get_is_live_update_enabled()\n       ) {\n        studio_init_flags |= FMOD_STUDIO_INIT_LIVEUPDATE;\n    }\n\n    if (p_settings->get_is_memory_tracking_enabled()) { studio_init_flags |= FMOD_STUDIO_INIT_MEMORY_TRACKING; }\n\n    FMOD_INITFLAGS init_flags = FMOD_INIT_3D_RIGHTHANDED;\n\n    if (ERROR_CHECK(system->initialize(p_settings->get_channel_count(), studio_init_flags, init_flags, nullptr))) {\n        isInitialized = true;\n        GODOT_LOG_INFO(\"FMOD Sound System: Successfully initialized\")\n\n        if ((studio_init_flags & FMOD_STUDIO_INIT_LIVEUPDATE) == FMOD_STUDIO_INIT_LIVEUPDATE) {\n            GODOT_LOG_INFO(\"FMOD Sound System: Live update enabled!\")\n        }\n\n        if ((studio_init_flags & FMOD_STUDIO_INIT_MEMORY_TRACKING) == FMOD_STUDIO_INIT_MEMORY_TRACKING) {\n            GODOT_LOG_INFO(\"FMOD Sound System: Memory tracking enabled!\")\n        }\n    }\n\n    if (ERROR_CHECK(\n          coreSystem->setFileSystem(&Callbacks::godotFileOpen, &Callbacks::godotFileClose, nullptr, nullptr, &Callbacks::godotSyncRead, &Callbacks::godotSyncCancel, -1)\n        )) {\n        GODOT_LOG_VERBOSE(\"Custom File System enabled.\")\n    }\n    cache = new FmodCache(system, coreSystem);\n}\n\nvoid FmodServer::update() {\n    if (!isInitialized) {\n        if (!isNotInitializedPrinted) {\n            GODOT_LOG_ERROR(\"FMOD Sound System: Fmod should be initialized before calling update\")\n            isNotInitializedPrinted = true;\n        }\n        return;\n    }\n\n    // Check if bank are loaded, load buses, vca and event descriptions.\n    cache->update_pending();\n\n    callback_mutex->lock();\n    for (const Callback& callback : callbacks_to_process) {\n        if (!callback.callable.is_valid()) { continue; }// Don't run the callback if the object has been killed\n        godot::Array args = godot::Array();\n        args.append(callback.fmod_callback_properties);\n        args.append(callback.type);\n        callback.callable.callv(args);\n    }\n    callbacks_to_process.clear();\n    callback_mutex->unlock();\n\n    Vector<OneShot*> one_shots_copy = oneShots;\n    for (OneShot* oneShot : one_shots_copy) {\n        if (!oneShot->instance->is_valid() || !oneShot->wrapper.is_valid()) {\n            // We free oneShot when their event or Object is dead.\n            oneShots.erase(oneShot);\n            delete oneShot;\n            continue;\n        }\n        oneShot->instance->set_node_attributes(oneShot->wrapper.get_node());\n    }\n\n    Vector<Ref<FmodEvent>> events_copy = runningEvents;\n    for (const Ref<FmodEvent>& event : events_copy) {\n        if (!event->is_valid()) { runningEvents.erase(event); }\n    }\n\n#ifdef TOOLS_ENABLED\n    if (!Engine::get_singleton()->is_editor_hint()) {\n#endif\n        // Editor only needs to run the server for events preview in the explorer.\n        //  We don't need to update performance_data and listeners\n        _set_listener_attributes();\n        _update_performance_data();\n#ifdef TOOLS_ENABLED\n    }\n#endif\n\n    ERROR_CHECK(system->update());\n}\n\nvoid FmodServer::_set_listener_attributes() {\n    for (int i = 0; i < systemListenerNumber; ++i) {\n        Listener* listener = &listeners[i];\n        if (listener->listenerLock) { continue; }\n        if (!listener->wrapper.is_valid()) {\n            listener->wrapper.set_node(nullptr);\n            ERROR_CHECK_WITH_REASON(system->setListenerWeight(i, 0), vformat(\"Cannot set listener %d weight to 0\", i));\n            continue;\n        }\n\n        Node* node {listener->wrapper.get_node()};\n        if (!node->is_inside_tree()) { return; }\n\n        if (auto* ci {Node::cast_to<CanvasItem>(node)}) {\n            FMOD_3D_ATTRIBUTES attr = get_3d_attributes_from_transform2d(ci->get_global_transform(), distanceScale);\n            ERROR_CHECK_WITH_REASON(system->setListenerAttributes(i, &attr), vformat(\"Cannot set listener %d attributes\", i));\n            continue;\n        }\n\n        if (auto* s {Node::cast_to<Node3D>(node)}) {\n            FMOD_3D_ATTRIBUTES attr = get_3d_attributes_from_transform3d(s->get_global_transform(), distanceScale);\n            ERROR_CHECK_WITH_REASON(system->setListenerAttributes(i, &attr), vformat(\"Cannot set listener %d attributes\", i));\n            continue;\n        }\n    }\n}\n\nvoid FmodServer::shutdown() {\n    if (!isInitialized) { return; }\n\n    FMOD::Debug_Initialize(FMOD_DEBUG_LEVEL_ERROR | FMOD_DEBUG_LEVEL_WARNING, FMOD_DEBUG_MODE_TTY, nullptr, nullptr);\n\n    isInitialized = false;\n    isNotInitializedPrinted = false;\n    ERROR_CHECK(system->unloadAll());\n    ERROR_CHECK(system->release());\n    system = nullptr;\n    coreSystem = nullptr;\n    delete cache;\n    cache = nullptr;\n    GODOT_LOG_INFO(\"FMOD Sound System: System released\")\n}\n\nvoid FmodServer::set_system_listener_number(int p_listenerNumber) {\n    if (p_listenerNumber > 0 && p_listenerNumber <= FMOD_MAX_LISTENERS) {\n        if (ERROR_CHECK_WITH_REASON(system->setNumListeners(p_listenerNumber), vformat(\"Cannot set listener count to %d\", p_listenerNumber))) {\n            systemListenerNumber = p_listenerNumber;\n        }\n    } else {\n        GODOT_LOG_ERROR(\"Number of listeners must be set between 1 and 8\")\n    }\n}\n\nvoid FmodServer::add_listener(int index, Node* game_obj) {\n    if (index >= 0 && index < systemListenerNumber) {\n        Listener* listener = &listeners[index];\n        listener->wrapper.set_node(game_obj);\n        ERROR_CHECK_WITH_REASON(\n          system->setListenerWeight(index, listener->weight),\n          vformat(\"Cannot set listener %d weight to %f\", index, listener->weight)\n        );\n    } else {\n        GODOT_LOG_ERROR(\"index of listeners must be set between 0 and the number of listeners set\")\n    }\n}\n\nvoid FmodServer::remove_listener(int index, Node* game_obj) {\n    if (index >= 0 && index < systemListenerNumber) {\n        Listener* listener = &listeners[index];\n\n        if (listener->wrapper.get_node() != game_obj) { return; }\n\n        listener->wrapper.set_node(nullptr);\n        ERROR_CHECK_WITH_REASON(system->setListenerWeight(index, 0), vformat(\"Cannot set listener %d weight to 0\", index));\n    } else {\n        GODOT_LOG_ERROR(\"index of listeners must be set between 0 and the number of listeners set\")\n    }\n}\n\nint FmodServer::get_system_listener_number() const {\n    return systemListenerNumber;\n}\n\nfloat FmodServer::get_system_listener_weight(const int index) {\n    if (index >= 0 && index < systemListenerNumber) {\n        float weight = 0;\n        ERROR_CHECK_WITH_REASON(system->getListenerWeight(index, &weight), vformat(\"Cannot get listener %d weight\", index));\n        listeners[index].weight = weight;\n        return weight;\n    } else {\n        GODOT_LOG_ERROR(\"index of listeners must be set between 0 and the number of listeners set\")\n        return 0;\n    }\n}\n\nvoid FmodServer::set_system_listener_weight(const int index, float weight) {\n    if (index >= 0 && index < systemListenerNumber) {\n        listeners[index].weight = weight;\n        ERROR_CHECK_WITH_REASON(system->setListenerWeight(index, weight), vformat(\"Cannot set listener %d weight to %f\", index, weight));\n    } else {\n        GODOT_LOG_ERROR(\"index of listeners must be set between 0 and the number of listeners set\")\n    }\n}\n\nTransform3D FmodServer::get_listener_transform3d(int index) {\n    Transform3D transform;\n    if (index >= 0 && index < systemListenerNumber) {\n        FMOD_3D_ATTRIBUTES attr;\n        ERROR_CHECK_WITH_REASON(system->getListenerAttributes(index, &attr), vformat(\"Cannot get listener %d transform3d\", index));\n        transform = get_transform3d_from_3d_attributes(attr, distanceScale);\n    } else {\n        GODOT_LOG_ERROR(\"index of listeners must be set between 0 and the number of listeners set\")\n    }\n    return transform;\n}\n\nTransform2D FmodServer::get_listener_transform2d(int index) {\n    Transform2D transform;\n    if (index >= 0 && index < systemListenerNumber) {\n        FMOD_3D_ATTRIBUTES attr;\n        ERROR_CHECK_WITH_REASON(system->getListenerAttributes(index, &attr), vformat(\"Cannot get listener %d transform2d\", index));\n        transform = get_transform2d_from_3d_attributes(attr, distanceScale);\n    } else {\n        GODOT_LOG_ERROR(\"index of listeners must be set between 0 and the number of listeners set\")\n    }\n    return transform;\n}\n\nVector3 FmodServer::get_listener_3d_velocity(int index) {\n    Vector3 velocity;\n    if (index >= 0 && index < systemListenerNumber) {\n        FMOD_3D_ATTRIBUTES attr;\n        ERROR_CHECK_WITH_REASON(system->getListenerAttributes(index, &attr), vformat(\"Cannot get listener %d velocity\", index));\n        velocity = get_velocity3d_from_3d_attributes(attr, distanceScale);\n    } else {\n        GODOT_LOG_ERROR(\"index of listeners must be set between 0 and the number of listeners set\")\n    }\n    return velocity;\n}\n\nVector2 FmodServer::get_listener_2d_velocity(int index) {\n    Vector2 velocity;\n    if (index >= 0 && index < systemListenerNumber) {\n        FMOD_3D_ATTRIBUTES attr;\n        ERROR_CHECK_WITH_REASON(system->getListenerAttributes(index, &attr), vformat(\"Cannot get listener %d velocity\", index));\n        velocity = get_velocity2d_from_3d_attributes(attr, distanceScale);\n    } else {\n        GODOT_LOG_ERROR(\"index of listeners must be set between 0 and the number of listeners set\")\n    }\n    return velocity;\n}\n\nvoid FmodServer::set_listener_transform3d(int index, const Transform3D& transform) {\n    if (index >= 0 && index < systemListenerNumber) {\n        FMOD_3D_ATTRIBUTES attr = get_3d_attributes_from_transform3d(transform, distanceScale);\n        ERROR_CHECK_WITH_REASON(system->setListenerAttributes(index, &attr), vformat(\"Cannot set listener %d transform3d\", index));\n    } else {\n        GODOT_LOG_ERROR(\"index of listeners must be set between 0 and the number of listeners set\")\n    }\n}\n\nvoid FmodServer::set_listener_transform2d(int index, const Transform2D& transform) {\n    if (index >= 0 && index < systemListenerNumber) {\n        FMOD_3D_ATTRIBUTES attr = get_3d_attributes_from_transform2d(transform, distanceScale);\n        ERROR_CHECK_WITH_REASON(system->setListenerAttributes(index, &attr), vformat(\"Cannot set listener %d transform2d\", index));\n    } else {\n        GODOT_LOG_ERROR(\"index of listeners must be set between 0 and the number of listeners set\")\n    }\n}\n\nvoid FmodServer::set_listener_lock(int index, bool isLocked) {\n    if (index >= 0 && index < systemListenerNumber) {\n        listeners[index].listenerLock = isLocked;\n    } else {\n        GODOT_LOG_ERROR(\"index of listeners must be set between 0 and the number of listeners set\")\n    }\n}\n\nbool FmodServer::get_listener_lock(int index) {\n    if (index >= 0 && index < systemListenerNumber) {\n        return listeners[index].listenerLock;\n    } else {\n        GODOT_LOG_ERROR(\"index of listeners must be set between 0 and the number of listeners set\")\n        return false;\n    }\n}\n\nObject* FmodServer::get_object_attached_to_listener(const int index) {\n    if (index < 0 || index >= systemListenerNumber) {\n        GODOT_LOG_ERROR(\"index of listeners must be set between 0 and the number of listeners set\")\n        return nullptr;\n    }\n    Object* node = listeners[index].wrapper.get_node();\n    return node;\n}\n\nvoid FmodServer::set_software_format(const Ref<FmodSoftwareFormatSettings>& p_settings) {\n    if (system == nullptr && coreSystem == nullptr) {\n        ERROR_CHECK(FMOD::Studio::System::create(&system));\n        ERROR_CHECK(system->getCoreSystem(&coreSystem));\n    }\n    ERROR_CHECK(coreSystem->setSoftwareFormat(\n      p_settings->get_sample_rate(),\n      static_cast<FMOD_SPEAKERMODE>(p_settings->get_speaker_mode()),\n      p_settings->get_raw_speakers_count()\n    ));\n}\n\nRef<FmodBank> FmodServer::load_bank(const String& pathToBank, unsigned int flag) {\n    if (cache->has_bank(pathToBank)) { return cache->get_bank(pathToBank); }// bank is already loaded\n\n#ifdef DEBUG_ENABLED\n    if (!FileAccess::file_exists(pathToBank)) {\n        GODOT_LOG_ERROR(vformat(\"Cannot load bank at %s\", pathToBank))\n        return {};\n    }\n#endif\n\n    return cache->add_bank(pathToBank, flag);\n}\n\nvoid FmodServer::unload_bank(const String& pathToBank) {\n#ifdef DEBUG_ENABLED\n    if (!FileAccess::file_exists(pathToBank)) {\n        GODOT_LOG_ERROR(vformat(\"Cannot unload bank at %s\", pathToBank))\n        return;\n    }\n#endif\n\n    cache->remove_bank(pathToBank);\n}\n\nbool FmodServer::banks_still_loading() {\n    return cache->is_loading();\n}\n\n#ifdef IOS_ENABLED\nuint32_t register_ios_dsp(FMOD_SYSTEM_PTR system, FMOD_DSP_DESCRIPTION* description, uint32_t* handle) {\n    return reinterpret_cast<FMOD::System*>(system)->registerDSP(description, handle);\n}\n\nuint32_t register_ios_codec(FMOD_SYSTEM_PTR system, FMOD_CODEC_DESCRIPTION* description, uint32_t* handle) {\n    return reinterpret_cast<FMOD::System*>(system)->registerCodec(description, handle);\n}\n\nuint32_t register_ios_output(FMOD_SYSTEM_PTR system, FMOD_OUTPUT_DESCRIPTION* description, uint32_t* handle) {\n    return reinterpret_cast<FMOD::System*>(system)->registerOutput(description, handle);\n}\n#endif\n\nvoid FmodServer::load_all_plugins(const Ref<FmodPluginsSettings>& p_settings) {\n#ifndef IOS_ENABLED\n    Vector<String> plugin_paths = get_fmod_plugins_libraries_paths(p_settings);\n    for (const String& path : plugin_paths) {\n#ifdef DEBUG_ENABLED\n        GODOT_LOG_INFO(vformat(\"Will load %s\", path));\n#endif\n        load_plugin(path);\n    }\n#else\n    FMOD_IOS_INTERFACE interface {\n      .system = coreSystem,\n      .register_dsp_method = &register_ios_dsp,\n      .register_codec_method = &register_ios_codec,\n      .register_output_method = &register_ios_output\n    };\n\n    uint32_t plugin_count;\n    uint32_t* plugin_handles = load_all_fmod_plugins(&interface, &plugin_count);\n    for (uint32_t i = 0; i < plugin_count; ++i) {\n        cache->add_plugin(plugin_handles[i]);\n    }\n    std::free(plugin_handles);\n#endif\n}\n\nuint32_t FmodServer::load_plugin(const String& p_plugin_path, uint32_t p_priority) {\n#ifndef IOS_ENABLED\n    return cache->add_plugin(p_plugin_path, p_priority);\n#endif\n    return 0xFFFFFFFF;\n}\n\nvoid FmodServer::unload_plugin(uint32_t p_plugin_handle) {\n    cache->remove_plugin(p_plugin_handle);\n}\n\nbool FmodServer::is_plugin_loaded(uint32_t p_plugin_handle) {\n    return cache->has_plugin(p_plugin_handle);\n}\n\nbool FmodServer::check_vca_guid(const String& guid) {\n    return cache->has_vca_guid(string_to_fmod_guid(guid.utf8().get_data()));\n}\n\nbool FmodServer::check_vca_path(const String& vcaPath) {\n    return cache->has_vca_path(vcaPath);\n}\n\nbool FmodServer::check_bus_guid(const String& guid) {\n    return cache->has_bus_guid(string_to_fmod_guid(guid.utf8().get_data()));\n}\n\nbool FmodServer::check_bus_path(const String& busPath) {\n    return cache->has_bus_path(busPath);\n}\n\nbool FmodServer::check_event_guid(const String& guid) {\n    return cache->has_event_guid(string_to_fmod_guid(guid.utf8().get_data()));\n}\n\nbool FmodServer::check_event_guid_internal(const FMOD_GUID& guid) {\n    return cache->has_event_guid(guid);\n}\n\nbool FmodServer::check_event_path(const String& eventPath) {\n    return cache->has_event_path(eventPath);\n}\n\nRef<FmodVCA> FmodServer::get_vca_from_guid(const String& guid) {\n    return cache->get_vca(string_to_fmod_guid(guid.utf8().get_data()));\n}\n\nRef<FmodVCA> FmodServer::get_vca(const String& vcaPath) {\n    return cache->get_vca(vcaPath);\n}\n\nRef<FmodBus> FmodServer::get_bus_from_guid(const String& guid) {\n    return cache->get_bus(string_to_fmod_guid(guid.utf8().get_data()));\n}\n\nRef<FmodBus> FmodServer::get_bus(const String& busPath) {\n    return cache->get_bus(busPath);\n}\n\nRef<FmodEventDescription> FmodServer::get_event_from_guid(const String& guid) {\n    return cache->get_event(string_to_fmod_guid(guid.utf8().get_data()));\n}\n\nRef<FmodEventDescription> FmodServer::get_event_from_guid_internal(const FMOD_GUID& guid) {\n    return cache->get_event(guid);\n}\n\nRef<FmodEventDescription> FmodServer::get_event(const String& eventPath) {\n    return cache->get_event(eventPath);\n}\n\nFMOD_GUID FmodServer::get_event_guid_internal(const String& event_path) {\n    return cache->get_event_guid(event_path);\n}\n\nString FmodServer::get_event_guid(const String& event_path) {\n    return fmod_guid_to_string(get_event_guid_internal(event_path));\n}\n\nString FmodServer::get_event_path_internal(const FMOD_GUID& guid) {\n    return cache->get_event_path(guid);\n}\n\nString FmodServer::get_event_path(const String& guid) {\n    return cache->get_event_path(string_to_fmod_guid(guid.utf8().get_data()));\n}\n\nArray FmodServer::get_all_vca() {\n    Array array;\n    for (KeyValue<FMOD_GUID, Ref<FmodVCA>>& entry : cache->vcas) {\n        array.append(entry.value);\n    }\n    return array;\n}\n\nArray FmodServer::get_all_buses() {\n    Array array;\n    for (KeyValue<FMOD_GUID, Ref<FmodBus>>& entry : cache->buses) {\n        array.append(entry.value);\n    }\n    return array;\n}\n\nArray FmodServer::get_all_event_descriptions() {\n    Array array;\n    for (KeyValue<FMOD_GUID, Ref<FmodEventDescription>>& entry : cache->event_descriptions) {\n        array.append(entry.value);\n    }\n    return array;\n}\n\nArray FmodServer::get_all_banks() {\n    Array array;\n    for (KeyValue<String, FmodBank*>& entry : cache->banks) {\n        array.append(Ref<FmodBank>(entry.value));\n    }\n    return array;\n}\n\nRef<FmodEvent> FmodServer::create_event_instance_with_guid(const String& guid) {\n    return create_event_instance_with_guid_internal(string_to_fmod_guid(guid.utf8().get_data()));\n}\n\nRef<FmodEvent> FmodServer::create_event_instance_with_guid_internal(const FMOD_GUID& guid) {\n    EventIdentifier parameters {};\n    parameters.guid = guid;\n    return _create_event_instance<EventIdentifierType::GUID>(parameters);\n}\n\nRef<FmodEvent> FmodServer::create_event_instance(const String& eventPath) {\n    return _create_event_instance<EventIdentifierType::PATH>({eventPath.utf8().get_data()});\n}\n\nRef<FmodEvent> FmodServer::create_event_instance_from_description(const Ref<FmodEventDescription>& event_description) {\n    EventIdentifier parameter {};\n    parameter.event_description = event_description.ptr();\n    return _create_event_instance<EventIdentifierType::EVENT_DESCRIPTION>(parameter);\n}\n\nRef<FmodEventDescription> FmodServer::_get_event_description(const String& event_name) {\n    bool found = cache->has_event_path(event_name);\n    if (!found) {\n        GODOT_LOG_WARNING(\"Event \" + event_name + \" can't be found. Check if the path is correct or the bank properly loaded.\")\n        return {};\n    }\n\n    return cache->get_event(event_name);\n}\n\nRef<FmodEventDescription> FmodServer::_get_event_description(const FMOD_GUID& guid) {\n    bool found = cache->has_event_guid(guid);\n    if (!found) {\n        String fmod_guid_string {fmod_guid_to_string(guid)};\n        GODOT_LOG_WARNING(\"Event \" + fmod_guid_string + \" can't be found. Check if the path is correct or the bank properly loaded.\")\n        return {};\n    }\n\n    return cache->get_event(guid);\n}\n\nvoid FmodServer::play_one_shot_using_guid(const String& guid) {\n    EventIdentifier parameter {};\n    parameter.guid = string_to_fmod_guid(guid.utf8().get_data());\n    return _play_one_shot<EventIdentifierType::GUID>(parameter, nullptr);\n}\n\nvoid FmodServer::play_one_shot(const String& event_name) {\n    return _play_one_shot<EventIdentifierType::PATH>({event_name.utf8().get_data()}, nullptr);\n}\n\nvoid FmodServer::play_one_shot_using_event_description(const Ref<FmodEventDescription>& event_description) {\n    EventIdentifier parameter {};\n    parameter.event_description = event_description.ptr();\n    return _play_one_shot<EventIdentifierType::EVENT_DESCRIPTION>(parameter, nullptr);\n}\n\nvoid FmodServer::play_one_shot_using_guid_with_params(const String& guid, const Dictionary& parameters) {\n    EventIdentifier parameter {};\n    parameter.guid = string_to_fmod_guid(guid.utf8().get_data());\n\n    return _play_one_shot<EventIdentifierType::GUID>(parameter, nullptr, parameters);\n}\n\nvoid FmodServer::play_one_shot_with_params(const String& event_name, const Dictionary& parameters) {\n    return _play_one_shot<EventIdentifierType::PATH>({event_name.utf8().get_data()}, nullptr, parameters);\n}\n\nvoid FmodServer::play_one_shot_using_event_description_with_params(const Ref<FmodEventDescription>& event_description, const Dictionary& parameters) {\n    EventIdentifier parameter {};\n    parameter.event_description = event_description.ptr();\n\n    return _play_one_shot<EventIdentifierType::EVENT_DESCRIPTION>(parameter, nullptr, parameters);\n}\n\nvoid FmodServer::play_one_shot_using_guid_attached(const String& guid, Node* game_obj) {\n    EventIdentifier parameter {};\n    parameter.guid = string_to_fmod_guid(guid.utf8().get_data());\n    return _play_one_shot<EventIdentifierType::GUID>(parameter, game_obj);\n}\n\nvoid FmodServer::play_one_shot_attached(const String& event_name, Node* game_obj) {\n    return _play_one_shot<EventIdentifierType::PATH>({event_name.utf8().get_data()}, game_obj);\n}\n\nvoid FmodServer::play_one_shot_using_event_description_attached(const Ref<FmodEventDescription>& event_description, Node* game_obj) {\n    EventIdentifier parameter {};\n    parameter.event_description = event_description.ptr();\n    return _play_one_shot<EventIdentifierType::EVENT_DESCRIPTION>(parameter, game_obj);\n}\n\nvoid FmodServer::play_one_shot_using_guid_attached_with_params(const String& guid, Node* game_obj, const Dictionary& parameters) {\n    EventIdentifier parameter {};\n    parameter.guid = string_to_fmod_guid(guid.utf8().get_data());\n    return _play_one_shot<EventIdentifierType::GUID>(parameter, game_obj, parameters);\n}\n\nvoid FmodServer::play_one_shot_attached_with_params(const String& event_name, Node* game_obj, const Dictionary& parameters) {\n    return _play_one_shot<EventIdentifierType::PATH>({event_name.utf8().get_data()}, game_obj, parameters);\n}\n\nvoid FmodServer::play_one_shot_using_event_description_attached_with_params(\n  const Ref<FmodEventDescription>& event_description,\n  Node* game_obj,\n  const Dictionary& parameters\n) {\n    EventIdentifier parameter {};\n    parameter.event_description = event_description.ptr();\n\n    return _play_one_shot<EventIdentifierType::EVENT_DESCRIPTION>(parameter, game_obj, parameters);\n}\n\nvoid FmodServer::set_system_dsp_buffer_size(const Ref<FmodDspSettings>& p_settings) {\n    unsigned int buffer_length = p_settings->get_dsp_buffer_size();\n    int num_buffers = p_settings->get_dsp_buffer_count();\n\n    if (buffer_length > 0 && num_buffers > 0 && ERROR_CHECK(coreSystem->setDSPBufferSize(buffer_length, num_buffers))) {\n        GODOT_LOG_VERBOSE(\"FMOD Sound System: Successfully set DSP buffer size\")\n    } else {\n        GODOT_LOG_ERROR(vformat(\"FMOD Sound System: Failed to set DSP buffer size: %s, with buffer count: %s\", buffer_length, num_buffers))\n    }\n}\n\nRef<FmodDspSettings> FmodServer::get_system_dsp_buffer_settings() {\n    unsigned int buffer_length;\n    int num_buffers;\n    Ref<FmodDspSettings> ret;\n    ret.instantiate();\n    ERROR_CHECK(coreSystem->getDSPBufferSize(&buffer_length, &num_buffers));\n    ret->set_dsp_buffer_size(buffer_length);\n    ret->set_dsp_buffer_count(num_buffers);\n    return ret;\n}\n\nunsigned int FmodServer::get_system_dsp_buffer_length() {\n    unsigned int bufferLength;\n    int numBuffers;\n    ERROR_CHECK(coreSystem->getDSPBufferSize(&bufferLength, &numBuffers));\n    return bufferLength;\n}\n\nint FmodServer::get_system_dsp_num_buffers() {\n    unsigned int bufferLength;\n    int numBuffers;\n    ERROR_CHECK(coreSystem->getDSPBufferSize(&bufferLength, &numBuffers));\n    return numBuffers;\n}\n\nvoid FmodServer::pause_all_events() {\n    for (const Ref<FmodEvent>& eventInstance : runningEvents) {\n        eventInstance->set_paused(true);\n    }\n}\n\nvoid FmodServer::unpause_all_events() {\n    for (const Ref<FmodEvent>& eventInstance : runningEvents) {\n        eventInstance->set_paused(false);\n    }\n}\n\nvoid FmodServer::mute_all_events() {\n    if (cache->is_master_loaded()) {\n        FMOD::Studio::Bus* masterBus = nullptr;\n        if (ERROR_CHECK(system->getBus(\"bus:/\", &masterBus))) { masterBus->setMute(true); }\n    }\n}\n\nvoid FmodServer::unmute_all_events() {\n    if (cache->is_master_loaded()) {\n        FMOD::Studio::Bus* masterBus = nullptr;\n        if (ERROR_CHECK(system->getBus(\"bus:/\", &masterBus))) { masterBus->setMute(false); }\n    }\n}\n\nvoid FmodServer::mixer_suspend() {\n    ERROR_CHECK(coreSystem->mixerSuspend());\n}\n\nvoid FmodServer::mixer_resume() {\n    ERROR_CHECK(coreSystem->mixerResume());\n}\n\nRef<FmodFile> FmodServer::load_file_as_sound(const String& path) {\n    if (cache->has_file(path)) {\n        GODOT_LOG_WARNING(\"FMOD Sound System: FILE ALREADY LOADED AS SOUND\" + String(path))\n        return cache->get_file(path);\n    }\n    return cache->add_file(path, FMOD_CREATESAMPLE);\n}\n\nRef<FmodFile> FmodServer::load_file_as_music(const String& path) {\n    if (cache->has_file(path)) {\n        GODOT_LOG_WARNING(\"FMOD Sound System: FILE ALREADY LOADED AS MUSIC\" + String(path))\n        return cache->get_file(path);\n    }\n    return cache->add_file(path, (FMOD_CREATESTREAM | FMOD_LOOP_NORMAL));\n}\n\nvoid FmodServer::unload_file(const String& path) {\n    if (!cache->has_file(path)) {\n        GODOT_LOG_WARNING(\"File \" + path + \" can't be found. Check if it was properly loaded or already unloaded.\")\n        return;\n    }\n    cache->remove_file(path);\n    GODOT_LOG_VERBOSE(\"FMOD Sound System: UNLOADING FILE\" + String(path))\n}\n\nRef<FmodSound> FmodServer::create_sound_instance(const String& path) {\n    if (!cache->has_file(path)) {\n        GODOT_LOG_WARNING(\"File \" + path + \" can't be found. Check if it was properly loaded.\")\n        return {};\n    }\n\n    Ref<FmodFile> file = cache->get_file(path);\n    FMOD::Channel* channel = nullptr;\n    ERROR_CHECK_WITH_REASON(coreSystem->playSound(file->get_wrapped(), nullptr, true, &channel), vformat(\"Cannot play sound %s\", path));\n    if (channel) {\n        Ref<FmodSound> ref = FmodSound::create_ref(channel);\n        return ref;\n    }\n    return {};\n}\n\nFMOD_STUDIO_SOUND_INFO FmodServer::get_sound_info(const String& sound_key) const {\n    FMOD_STUDIO_SOUND_INFO sound_info;\n    ERROR_CHECK_WITH_REASON(system->getSoundInfo(sound_key.utf8().get_data(), &sound_info), vformat(\"Cannot get sound info for %s\", sound_key));\n    return sound_info;\n}\n\nFMOD::Sound* FmodServer::create_sound(FMOD_STUDIO_SOUND_INFO& sound_info, FMOD_MODE mode) const {\n    FMOD::Sound* sound {nullptr};\n    ERROR_CHECK_WITH_REASON(\n      coreSystem->createSound(sound_info.name_or_data, mode | sound_info.mode, &sound_info.exinfo, &sound),\n      vformat(\"Cannot create sound %s with mode %s\", sound_info.name_or_data, mode)\n    );\n    return sound;\n}\n\nvoid FmodServer::set_sound_3d_settings(const Ref<FmodSound3DSettings>& p_settings) {\n    float distance_factor = p_settings->get_distance_factor();\n    if (distance_factor <= 0) {\n        GODOT_LOG_ERROR(\"FMOD Sound System: Failed to set 3D settings - invalid distance factor!\")\n    } else if (ERROR_CHECK(coreSystem->set3DSettings(p_settings->get_doppler_scale(), distance_factor, p_settings->get_rolloff_scale()))) {\n        distanceScale = distance_factor;\n        GODOT_LOG_VERBOSE(\"FMOD Sound System: Successfully set global 3D settings\")\n    } else {\n        GODOT_LOG_ERROR(\"FMOD Sound System: Failed to set 3D settings\")\n    }\n}\n\nvoid FmodServer::wait_for_all_loads() {\n    ERROR_CHECK(system->flushSampleLoading());\n    cache->update_pending();\n}\n\nArray FmodServer::get_available_drivers() {\n    Array driverList;\n    int numDrivers = 0;\n\n    ERROR_CHECK(coreSystem->getNumDrivers(&numDrivers));\n\n    for (int i = 0; i < numDrivers; ++i) {\n        char name[MAX_DRIVER_NAME_SIZE];\n        int sampleRate;\n        FMOD_SPEAKERMODE speakerMode;\n        int speakerModeChannels;\n        ERROR_CHECK(coreSystem->getDriverInfo(i, name, MAX_DRIVER_NAME_SIZE, nullptr, &sampleRate, &speakerMode, &speakerModeChannels));\n        String nameStr(name);\n\n        Dictionary driverInfo;\n        driverInfo[\"id\"] = i;\n        driverInfo[\"name\"] = nameStr;\n        driverInfo[\"sample_rate\"] = sampleRate;\n        driverInfo[\"speaker_mode\"] = (int) speakerMode;\n        driverInfo[\"number_of_channels\"] = speakerModeChannels;\n        driverList.push_back(driverInfo);\n    }\n\n    return driverList;\n}\n\nint FmodServer::get_driver() {\n    int driverId = -1;\n    ERROR_CHECK(coreSystem->getDriver(&driverId));\n    return driverId;\n}\n\nvoid FmodServer::set_driver(const int id) {\n    ERROR_CHECK(coreSystem->setDriver(id));\n}\n\nvoid FmodServer::_update_performance_data() {\n    // get the CPU usage\n    FMOD_STUDIO_CPU_USAGE studioCpuUsage;\n    FMOD_CPU_USAGE cpuUsage;\n    ERROR_CHECK(system->getCPUUsage(&studioCpuUsage, &cpuUsage));\n    performanceData->dsp = cpuUsage.dsp;\n    performanceData->geometry = cpuUsage.geometry;\n    performanceData->stream = cpuUsage.stream;\n    performanceData->update = cpuUsage.update;\n    performanceData->convolution1 = cpuUsage.convolution1;\n    performanceData->convolution2 = cpuUsage.convolution2;\n    performanceData->studio = studioCpuUsage.update;\n\n    // get the memory usage\n    ERROR_CHECK(FMOD::Memory_GetStats(&performanceData->currently_allocated, &performanceData->max_allocated));\n\n    // get the file usage\n    long long sampleBytesRead = 0;\n    long long streamBytesRead = 0;\n    long long otherBytesRead = 0;\n    ERROR_CHECK(coreSystem->getFileUsage(&sampleBytesRead, &streamBytesRead, &otherBytesRead));\n    performanceData->sample_bytes_read = static_cast<int>(sampleBytesRead);\n    performanceData->stream_bytes_read = static_cast<int>(streamBytesRead);\n    performanceData->other_bytes_read = static_cast<int>(otherBytesRead);\n}\n\nRef<FmodPerformanceData> FmodServer::get_performance_data() {\n    return performanceData;\n}\n\nvoid FmodServer::set_global_parameter_by_name(const String& parameter_name, float value) {\n    ERROR_CHECK_WITH_REASON(\n      system->setParameterByName(parameter_name.utf8().get_data(), value),\n      vformat(\"Cannot set global parameter %s to value %f\", parameter_name, value)\n    );\n}\n\nvoid FmodServer::set_global_parameter_by_name_with_label(const String& parameter_name, const String& label) {\n    ERROR_CHECK_WITH_REASON(\n      system->setParameterByNameWithLabel(parameter_name.utf8().get_data(), label.utf8().get_data()),\n      vformat(\"Cannot set global parameter %s to value %s\", parameter_name, label)\n    );\n}\n\nfloat FmodServer::get_global_parameter_by_name(const String& parameter_name) {\n    float value = 0.f;\n    ERROR_CHECK_WITH_REASON(\n      system->getParameterByName(parameter_name.utf8().get_data(), &value),\n      vformat(\"Cannot get global parameter %s\", parameter_name, value)\n    );\n    return value;\n}\n\nvoid FmodServer::set_global_parameter_by_id(uint64_t parameter_id, const float value) {\n    ERROR_CHECK_WITH_REASON(\n      system->setParameterByID(ulong_to_fmod_parameter_id(parameter_id), value),\n      vformat(\"Cannot set global parameter %d to value %f\", parameter_id, value)\n    );\n}\n\nvoid FmodServer::set_global_parameter_by_id_with_label(uint64_t parameter_id, const String& label) {\n    ERROR_CHECK_WITH_REASON(\n      system->setParameterByIDWithLabel(ulong_to_fmod_parameter_id(parameter_id), label.utf8().get_data()),\n      vformat(\"Cannot set global parameter %d to value %s\", parameter_id, label)\n    );\n}\n\nfloat FmodServer::get_global_parameter_by_id(uint64_t parameter_id) {\n    float value = -1.f;\n    ERROR_CHECK_WITH_REASON(\n      system->getParameterByID(ulong_to_fmod_parameter_id(parameter_id), &value),\n      vformat(\"Cannot set global parameter %d\", parameter_id, value)\n    );\n    return value;\n}\n\nDictionary FmodServer::get_global_parameter_desc_by_name(const String& parameter_name) {\n    Dictionary paramDesc;\n    FMOD_STUDIO_PARAMETER_DESCRIPTION\n    pDesc;\n    if (ERROR_CHECK_WITH_REASON(\n          system->getParameterDescriptionByName(parameter_name.utf8().get_data(), &pDesc),\n          vformat(\"Cannot get global parameter %s\", parameter_name)\n        )) {\n        paramDesc[\"name\"] = String(pDesc.name);\n        paramDesc[\"id_first\"] = pDesc.id.data1;\n        paramDesc[\"id_second\"] = pDesc.id.data2;\n        paramDesc[\"minimum\"] = pDesc.minimum;\n        paramDesc[\"maximum\"] = pDesc.maximum;\n        paramDesc[\"default_value\"] = pDesc.defaultvalue;\n    }\n\n    return paramDesc;\n}\n\nDictionary FmodServer::get_global_parameter_desc_by_id(uint64_t parameter_id) {\n    Dictionary paramDesc;\n    FMOD_STUDIO_PARAMETER_DESCRIPTION pDesc;\n    if (ERROR_CHECK_WITH_REASON(\n          system->getParameterDescriptionByID(ulong_to_fmod_parameter_id(parameter_id), &pDesc),\n          vformat(\"Cannot get global parameter %d\", parameter_id)\n        )) {\n        paramDesc[\"name\"] = String(pDesc.name);\n        paramDesc[\"id_first\"] = pDesc.id.data1;\n        paramDesc[\"id_second\"] = pDesc.id.data2;\n        paramDesc[\"minimum\"] = pDesc.minimum;\n        paramDesc[\"maximum\"] = pDesc.maximum;\n        paramDesc[\"default_value\"] = pDesc.defaultvalue;\n    }\n\n    return paramDesc;\n}\n\nint FmodServer::get_global_parameter_desc_count() {\n    int count = 0;\n    ERROR_CHECK(system->getParameterDescriptionCount(&count));\n    return count;\n}\n\nArray FmodServer::get_global_parameter_desc_list() {\n    Array a;\n    FMOD_STUDIO_PARAMETER_DESCRIPTION\n    descList[256];\n    int count = 0;\n    ERROR_CHECK(system->getParameterDescriptionList(descList, 256, &count));\n    for (int i = 0; i < count; ++i) {\n        auto pDesc = descList[i];\n        Dictionary paramDesc;\n        paramDesc[\"name\"] = String(pDesc.name);\n        paramDesc[\"id_first\"] = pDesc.id.data1;\n        paramDesc[\"id_second\"] = pDesc.id.data2;\n        paramDesc[\"minimum\"] = pDesc.minimum;\n        paramDesc[\"maximum\"] = pDesc.maximum;\n        paramDesc[\"default_value\"] = pDesc.defaultvalue;\n        a.append(paramDesc);\n    }\n    return a;\n}\n\nvoid FmodServer::add_callback(const Callback& callback) {\n    callback_mutex->lock();\n    callbacks_to_process.push_back(callback);\n    callback_mutex->unlock();\n}\n\nvoid FmodServer::_apply_parameter_dict_to_event(const Ref<FmodEvent>& p_event, const Dictionary& parameters) {\n    Array keys = parameters.keys();\n    for (int i = 0; i < keys.size(); ++i) {\n        Variant& key = keys[i];\n        const Variant& value = parameters[keys[i]];\n\n        if (key.get_type() == Variant::Type::INT) {\n            if (value.get_type() == Variant::Type::STRING) {\n                p_event->set_parameter_by_id_with_label(key, value);\n                continue;\n            }\n            p_event->set_parameter_by_id(key, value);\n            continue;\n        }\n\n        if (value.get_type() == Variant::Type::STRING) {\n            p_event->set_parameter_by_name_with_label(key, value);\n            continue;\n        }\n        p_event->set_parameter_by_name(key, value);\n    }\n}\n"
  },
  {
    "path": "src/fmod_server.h",
    "content": "#ifndef GODOTFMOD_FMOD_SERVER_H\n#define GODOTFMOD_FMOD_SERVER_H\n\n#include \"core/fmod_file.h\"\n#include \"core/fmod_sound.h\"\n#include \"data/performance_data.h\"\n#include \"fmod_cache.h\"\n#include \"resources/fmod_plugins_settings.h\"\n#include \"studio/fmod_bank.h\"\n#include \"studio/fmod_bus.h\"\n#include \"studio/fmod_event.h\"\n#include \"studio/fmod_event_description.h\"\n#include \"studio/fmod_vca.h\"\n#include \"templates/hash_map.hpp\"\n#include \"templates/local_vector.hpp\"\n#include \"templates/vector.hpp\"\n#include \"variant/string.hpp\"\n\n#include <callback/event_callbacks.h>\n#include <callback/file_callbacks.h>\n#include <helpers/constants.h>\n#include <resources/fmod_dsp_settings.h>\n#include <resources/fmod_settings.h>\n#include <resources/fmod_software_format_settings.h>\n#include <resources/fmod_sound_3d_settings.h>\n\n#include <classes/canvas_item.hpp>\n#include <classes/global_constants.hpp>\n#include <classes/mutex.hpp>\n#include <classes/object.hpp>\n#include <core/object.hpp>\n#include <fmod.hpp>\n#include <fmod_studio.hpp>\n#include <godot.hpp>\n#include <variant/utility_functions.hpp>\n\nnamespace godot {\n\n    struct OneShot {\n        NodeWrapper wrapper;\n        Ref<FmodEvent> instance;\n    };\n\n    struct Listener {\n        NodeWrapper wrapper;\n        bool listenerLock = false;\n        float weight = 1.0;\n    };\n\n    struct Callback {\n        FMOD_STUDIO_EVENT_CALLBACK_TYPE type;\n        godot::Callable callable;\n        Dictionary fmod_callback_properties;\n    };\n\n    class FmodServer : public Object {\n        GDCLASS(FmodServer, Object);\n\n        static FmodServer* singleton;\n\n    public:\n        union EventIdentifier {\n            const char* string_identifier;\n            FMOD_GUID guid;\n            FmodEventDescription* event_description;\n        };\n\n        enum EventIdentifierType {\n            PATH,\n            GUID,\n            EVENT_DESCRIPTION\n        };\n\n        union ParameterIdentifier {\n            String* name;\n            uint64_t id;\n        };\n\n        struct ParameterValue {\n            ParameterIdentifier identifier;\n            Variant value;\n            Variant::Type variant_type;\n            bool should_load_by_id;\n        };\n\n    private:\n        FMOD::Studio::System* system;\n        FMOD::System* coreSystem;\n\n        bool isInitialized;\n        bool isNotInitializedPrinted;\n\n        float distanceScale;\n\n        FmodCache* cache;\n\n        int systemListenerNumber = 1;\n        Listener listeners[FMOD_MAX_LISTENERS];\n\n        Vector<OneShot*> oneShots;\n        Vector<Ref<FmodEvent>> runningEvents;\n\n        Ref<FmodPerformanceData> performanceData;\n\n        // Direct call from fmod thread cannot be made as we cannot interact with scene tree from another thread (thread\n        // guard).\n        // call_deferred is not implemented in godot-cpp.\n        // Would have prefered a SpinLock but does not seems to exist in godot-cpp.\n        // TODO: Change when https://github.com/godotengine/godot-cpp/pull/1091 is merged.\n        Ref<Mutex> callback_mutex;\n        List<Callback> callbacks_to_process;\n\n\n        void _set_listener_attributes();\n\n        Ref<FmodEventDescription> _get_event_description(const String& event_name);\n        Ref<FmodEventDescription> _get_event_description(const FMOD_GUID& guid);\n\n        void _update_performance_data();\n\n    public:\n        FmodServer();\n\n        ~FmodServer() override;\n\n        static FmodServer* get_singleton();\n\n        // LIFECYCLE\n        void init(const Ref<FmodGeneralSettings>& p_settings);\n        void update();\n        void shutdown();\n\n        // SETTINGS\n        void set_software_format(const Ref<FmodSoftwareFormatSettings>& p_settings);\n        void set_sound_3d_settings(const Ref<FmodSound3DSettings>& p_settings);\n        void set_system_dsp_buffer_size(const Ref<FmodDspSettings>& p_settings);\n        Ref<FmodDspSettings> get_system_dsp_buffer_settings();\n        unsigned int get_system_dsp_buffer_length();\n        int get_system_dsp_num_buffers();\n\n        // OBJECTS\n        bool check_vca_guid(const String& guid);\n        bool check_vca_path(const String& vcaPath);\n        bool check_bus_guid(const String& guid);\n        bool check_bus_path(const String& busPath);\n        bool check_event_guid(const String& guid);\n        bool check_event_guid_internal(const FMOD_GUID& guid);\n        bool check_event_path(const String& eventPath);\n        Ref<FmodVCA> get_vca_from_guid(const String& guid);\n        Ref<FmodVCA> get_vca(const String& vcaPath);\n        Ref<FmodBus> get_bus_from_guid(const String& guid);\n        Ref<FmodBus> get_bus(const String& busPath);\n        Ref<FmodEventDescription> get_event_from_guid(const String& guid);\n        Ref<FmodEventDescription> get_event_from_guid_internal(const FMOD_GUID& guid);\n        Ref<FmodEventDescription> get_event(const String& eventPath);\n        FMOD_GUID get_event_guid_internal(const String& event_path);\n        String get_event_guid(const String& event_path);\n        String get_event_path_internal(const FMOD_GUID& guid);\n        String get_event_path(const String& guid);\n        Array get_all_vca();\n        Array get_all_buses();\n        Array get_all_event_descriptions();\n        Array get_all_banks();\n\n        // DEBUGGING\n        Array get_available_drivers();\n        int get_driver();\n        void set_driver(int id);\n        Ref<FmodPerformanceData> get_performance_data();\n\n        // GLOBAL PARAMETERS\n        void set_global_parameter_by_name(const String& parameter_name, float value);\n        void set_global_parameter_by_name_with_label(const String& parameter_name, const String& label);\n        float get_global_parameter_by_name(const String& parameter_name);\n        void set_global_parameter_by_id(uint64_t parameter_id, float value);\n        void set_global_parameter_by_id_with_label(uint64_t parameter_id, const String& label);\n        float get_global_parameter_by_id(uint64_t parameter_id);\n        Dictionary get_global_parameter_desc_by_name(const String& parameter_name);\n        Dictionary get_global_parameter_desc_by_id(uint64_t parameter_id);\n        int get_global_parameter_desc_count();\n        Array get_global_parameter_desc_list();\n\n        // LISTENERS\n        void add_listener(int index, Node* game_obj);\n        void remove_listener(int index, Node* game_obj);\n        void set_system_listener_number(int listenerNumber);\n        int get_system_listener_number() const;\n        float get_system_listener_weight(int index);\n        void set_system_listener_weight(int index, float weight);\n        Transform3D get_listener_transform3d(int index);\n        Transform2D get_listener_transform2d(int index);\n        Vector3 get_listener_3d_velocity(int index);\n        Vector2 get_listener_2d_velocity(int index);\n        void set_listener_transform3d(int index, const Transform3D& transform);\n        void set_listener_transform2d(int index, const Transform2D& transform);\n        void set_listener_lock(int index, bool isLocked);\n        bool get_listener_lock(int index);\n        Object* get_object_attached_to_listener(int index);\n\n        // BANKS\n        Ref<FmodBank> load_bank(const String& pathToBank, unsigned int flag);\n        void unload_bank(const String& pathToBank);\n        bool banks_still_loading();\n\n        // PLUGINS\n        void load_all_plugins(const Ref<FmodPluginsSettings>& p_settings);\n        uint32_t load_plugin(const String& p_plugin_path, uint32_t p_priority = 0);\n        void unload_plugin(uint32_t p_plugin_handle);\n        bool is_plugin_loaded(uint32_t p_plugin_handle);\n\n        // EVENTS\n    private:\n        template<EventIdentifierType parameter_type>\n        Ref<FmodEvent> _create_event_instance(const EventIdentifier& identifier);\n    public:\n        Ref<FmodEvent> create_event_instance_with_guid(const String& guid);\n        Ref<FmodEvent> create_event_instance_with_guid_internal(const FMOD_GUID& guid);\n        Ref<FmodEvent> create_event_instance(const String& eventPath);\n        Ref<FmodEvent> create_event_instance_from_description(const Ref<FmodEventDescription>& event_description);\n\n        // SOUNDS\n        Ref<FmodFile> load_file_as_sound(const String& path);\n        Ref<FmodFile> load_file_as_music(const String& path);\n        void unload_file(const String& path);\n        Ref<FmodSound> create_sound_instance(const String& path);\n        FMOD_STUDIO_SOUND_INFO get_sound_info(const String& sound_key) const;\n        FMOD::Sound* create_sound(FMOD_STUDIO_SOUND_INFO& sound_info, FMOD_MODE mode) const;\n\n        //CALLBACKS\n        void add_callback(const Callback& callback);\n\n        /* Helper methods */\n    private:\n        template<EventIdentifierType parameter_type>\n        Ref<FmodEventDescription> fetch_event_description(const EventIdentifier& identifier);\n        template<EventIdentifierType parameter_type>\n        void _play_one_shot(const EventIdentifier& identifier, Node* game_obj, const Dictionary& parameters = Dictionary());\n        static void _apply_parameter_dict_to_event(const Ref<FmodEvent>& p_event, const Dictionary& parameters);\n\n    public:\n        template<class TParameter>\n        void apply_parameter_list_to_event(const Ref<FmodEvent>& p_event, const LocalVector<TParameter>& parameters);\n\n        void play_one_shot(const String& event_name);\n        void play_one_shot_with_params(const String& event_name, const Dictionary& parameters);\n        void play_one_shot_attached(const String& event_name, Node* game_obj);\n        void play_one_shot_attached_with_params(const String& event_name, Node* game_obj, const Dictionary& parameters);\n\n        void play_one_shot_using_guid(const String& guid);\n        void play_one_shot_using_guid_with_params(const String& guid, const Dictionary& parameters);\n        void play_one_shot_using_guid_attached(const String& guid, Node* game_obj);\n        void play_one_shot_using_guid_attached_with_params(const String& guid, Node* game_obj, const Dictionary& parameters);\n\n        void play_one_shot_using_event_description(const Ref<FmodEventDescription>& event_description);\n        void play_one_shot_using_event_description_with_params(const Ref<FmodEventDescription>& event_description, const Dictionary& parameters);\n        void play_one_shot_using_event_description_attached(const Ref<FmodEventDescription>& event_description, Node* game_obj);\n        void play_one_shot_using_event_description_attached_with_params(const Ref<FmodEventDescription>& event_description, Node* game_obj, const Dictionary& parameters);\n\n        void pause_all_events();\n        void unpause_all_events();\n        void mute_all_events();\n        void unmute_all_events();\n        void mixer_suspend();\n        void mixer_resume();\n        void wait_for_all_loads();\n\n    protected:\n        static void _bind_methods();\n    };\n\n\n    template<FmodServer::EventIdentifierType parameter_type>\n    Ref<FmodEventDescription> FmodServer::fetch_event_description(const FmodServer::EventIdentifier& identifier){\n        Ref<FmodEventDescription> desc;\n        switch (parameter_type) {\n            case PATH:\n                desc = _get_event_description(identifier.string_identifier);\n                break;\n            case GUID:\n                desc = _get_event_description(identifier.guid);\n                break;\n            case EVENT_DESCRIPTION:\n                desc = Ref<FmodEventDescription>(identifier.event_description);\n                break;\n        }\n        return desc;\n    }\n\n    template<FmodServer::EventIdentifierType parameter_type>\n    Ref<FmodEvent> FmodServer::_create_event_instance(const EventIdentifier& identifier) {\n        FMOD::Studio::EventInstance* eventInstance = nullptr;\n        ERROR_CHECK(fetch_event_description<parameter_type>(identifier)->get_wrapped()->createInstance(&eventInstance));\n\n        Ref<FmodEvent> ref = FmodEvent::create_ref(eventInstance);\n        if (ref.is_null() || !ref->is_valid()) {\n            GODOT_LOG_WARNING(\"Event Instance is invalid.\")\n            return {};\n        }\n\n        ref->get_wrapped()->setUserData(ref.ptr());\n        ref->set_distance_scale(distanceScale);\n        runningEvents.push_back(ref);\n        return ref;\n    }\n\n    template<FmodServer::EventIdentifierType parameter_type>\n    void FmodServer::_play_one_shot(const FmodServer::EventIdentifier& identifier, Node* game_obj, const Dictionary& parameters) {\n        Ref<FmodEventDescription> desc =  fetch_event_description<parameter_type>(identifier);\n        if (!desc->is_one_shot()){\n            GODOT_LOG_WARNING(desc->get_path() + \" is not a OneShot event.\")\n            return;\n        }\n\n\n        Ref<FmodEvent> ref = _create_event_instance<parameter_type>(identifier);\n\n        if (ref.is_null()) { return; }\n\n        if (!parameters.is_empty()) {\n            // set the initial parameter values\n            _apply_parameter_dict_to_event(ref, parameters);\n        }\n\n        if(game_obj){\n            auto* oneShot = new OneShot {NodeWrapper {game_obj}, ref};\n            ref->set_node_attributes(game_obj);\n            oneShots.push_back(oneShot);\n        }\n\n        ref->start();\n        ref->release();\n    }\n\n    template<class TParameter>\n    void FmodServer::apply_parameter_list_to_event(const Ref<FmodEvent>& p_event, const LocalVector<TParameter>& parameters) {\n        for (const TParameter& parameter : parameters) {\n            if (parameter.should_load_by_id) {\n                if (parameter.variant_type == Variant::Type::STRING) {\n                    p_event->set_parameter_by_id_with_label(parameter.identifier.id, parameter.value);\n                    continue;\n                }\n                p_event->set_parameter_by_id(parameter.identifier.id, parameter.value);\n                continue;\n            }\n\n            if (parameter.variant_type == Variant::Type::STRING) {\n                p_event->set_parameter_by_name_with_label(*parameter.identifier.name, parameter.value);\n                continue;\n            }\n            p_event->set_parameter_by_name(*parameter.identifier.name, parameter.value);\n        }\n    }\n}// namespace godot\n\n#endif// GODOTFMOD_FMOD_SERVER_H\n"
  },
  {
    "path": "src/fmod_string_names.cpp",
    "content": "#include \"fmod_string_names.h\"\n\nusing namespace godot;\n\nFmodStringNames* FmodStringNames::instance = nullptr;\n\nvoid FmodStringNames::create() {\n    instance = memnew(FmodStringNames);\n}\n\nvoid FmodStringNames::free() {\n    memdelete(instance);\n    instance = nullptr;\n}\n\nFmodStringNames* FmodStringNames::get_instance() {\n    return instance;\n}\n\nFmodStringNames::FmodStringNames() : bank_path_property_name(\"bank_paths\"),\n  event_parameter_prefix_for_properties(EVENT_PARAMETER_PREFIX_FOR_PROPERTIES) {\n\n}\n"
  },
  {
    "path": "src/fmod_string_names.h",
    "content": "#ifndef GODOTFMOD_FMOD_STRING_NAMES_H\n#define GODOTFMOD_FMOD_STRING_NAMES_H\n\n\n#include \"godot.hpp\"\n#include \"variant/string_name.hpp\"\n\nusing namespace godot;\n\nclass FmodStringNames {\n    friend void initialize_fmod_module(ModuleInitializationLevel p_level);\n    friend void uninitialize_fmod_module(ModuleInitializationLevel p_level);\n\n    static void create();\n    static void free();\n\n    static FmodStringNames* instance;\n\n    FmodStringNames();\n\npublic:\n    static FmodStringNames* get_instance();\n\n    static constexpr const char* EVENT_PARAMETER_PREFIX_FOR_PROPERTIES = \"fmod_parameters\";\n\n    StringName bank_path_property_name;\n    StringName event_parameter_prefix_for_properties;\n};\n\n#endif //GODOTFMOD_FMOD_STRING_NAMES_H\n"
  },
  {
    "path": "src/helpers/common.h",
    "content": "#ifndef GODOTFMOD_COMMON_H\n#define GODOTFMOD_COMMON_H\n\n#include \"classes/canvas_item.hpp\"\n#include \"classes/node3d.hpp\"\n#include \"fmod_common.h\"\n#include \"variant/utility_functions.hpp\"\n\n#include <fmod_errors.h>\n#include <fmod_studio_common.h>\n#include <helpers/current_function.h>\n#include <cstdio>\n\n#include <godot.hpp>\n#include <variant/utility_functions.hpp>\n\n#define MAX_PATH_SIZE 512\n#define MAX_DRIVER_NAME_SIZE 256\n\n#define GODOT_LOG_INFO(message) UtilityFunctions::print(message);\n#define GODOT_LOG_VERBOSE(message) UtilityFunctions::print_verbose(message);\n#define GODOT_LOG_WARNING(message) UtilityFunctions::push_warning(message, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__);\n#define GODOT_LOG_ERROR(message) UtilityFunctions::push_error(message, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__);\n\n#define ERROR_CHECK_WITH_REASON(_result, _reason) \\\n(((_result) != FMOD_OK) ? (godot::UtilityFunctions::push_error(_reason, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__), false) : true)\n\n#define ERROR_CHECK(_result) ((_result) == FMOD_OK)\n\n#define FMODCLASS(m_class, m_inherits, m_owned)               \\\n    GDCLASS(m_class, m_inherits)                              \\\n                                                              \\\nprivate:                                                      \\\n    m_owned* _wrapped = nullptr;                              \\\n                                                              \\\npublic:                                                       \\\n    inline static Ref<m_class> create_ref(m_owned* wrapped) { \\\n        Ref<m_class> ref;                                     \\\n        if (wrapped) {                                        \\\n            ref.instantiate();                                \\\n            ref->_wrapped = wrapped;                          \\\n        }                                                     \\\n        return ref;                                           \\\n    }                                                         \\\n                                                              \\\n    bool is_valid() const {                                   \\\n        return _wrapped != nullptr && _wrapped->isValid();    \\\n    }                                                         \\\n                                                              \\\n    m_owned* get_wrapped() const {                            \\\n        return _wrapped;                                      \\\n    }                                                         \\\n                                                              \\\nprivate:\n\n#define FMODCLASSWITHPATH(m_class, m_inherits, m_owned)                  \\\n    GDCLASS(m_class, m_inherits)                                         \\\n                                                                         \\\nprivate:                                                                 \\\n    m_owned* _wrapped = nullptr;                                         \\\n    FMOD_GUID _guid;                                                     \\\n    String _path;                                                        \\\n                                                                         \\\npublic:                                                                  \\\n    inline static Ref<m_class> create_ref(m_owned* wrapped) {            \\\n        Ref<m_class> ref;                                                \\\n        if (wrapped) {                                                   \\\n            ref.instantiate();                                           \\\n            ref->_wrapped = wrapped;                                     \\\n            char path[MAX_PATH_SIZE];                                    \\\n            ERROR_CHECK(wrapped->getPath(path, MAX_PATH_SIZE, nullptr)); \\\n            ERROR_CHECK(wrapped->getID(&ref->_guid));                    \\\n            ref->_path = String(path);                                   \\\n        }                                                                \\\n        return ref;                                                      \\\n    }                                                                    \\\n                                                                         \\\n    bool is_valid() const {                                              \\\n        return _wrapped != nullptr && _wrapped->isValid();               \\\n    }                                                                    \\\n                                                                         \\\n    m_owned* get_wrapped() const {                                       \\\n        return _wrapped;                                                 \\\n    }                                                                    \\\n                                                                         \\\n    String get_path() const {                                            \\\n        return _path;                                                    \\\n    }                                                                    \\\n                                                                         \\\n    FMOD_GUID get_guid() const {                                         \\\n        return _guid;                                                    \\\n    }                                                                    \\\n                                                                         \\\n    String get_guid_as_string() const {                                  \\\n        return fmod_guid_to_string(_guid);                               \\\n    }                                                                    \\\n                                                                         \\\nprivate:\n\nnamespace godot {\n\n    class NodeWrapper {\n        Node* node {nullptr};\n        ObjectID id;\n\n        _FORCE_INLINE_ static bool is_spatial_node(Object* p_object) {\n            if (Node::cast_to<Node3D>(p_object) || Node::cast_to<CanvasItem>(p_object)) { return true; }\n            GODOT_LOG_ERROR(\"Invalid Object. A Godot object bound to FMOD has to be either a Node3D or CanvasItem.\")\n            return false;\n        }\n\n    public:\n        bool is_valid() const {\n            if (!node || !id.is_valid() || !UtilityFunctions::is_instance_id_valid(id)) { return false; }\n            return node->is_inside_tree();\n        }\n\n        Node* get_node() { return node; }\n\n        void set_node(Node* p_node) {\n            if (p_node) {\n                if (is_spatial_node(p_node)) {\n                    node = p_node;\n                    id = p_node->get_instance_id();\n                    return;\n                }\n            }\n            node = nullptr;\n            id = ObjectID();\n        }\n\n        NodeWrapper() = default;\n\n        explicit NodeWrapper(Node* p_node) { set_node(p_node); };\n    };\n\n    template<class T>\n    static inline Ref<T> create_ref() {\n        Ref<T> ref;\n        ref.instantiate();\n        return ref;\n    }\n\n    static inline FMOD_GUID string_to_fmod_guid(const char* guid) {\n        FMOD_GUID result;\n        sscanf(guid,\n               \"{%8x-%4hx-%4hx-%2hhx%2hhx-%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx}\",\n               &result.Data1, &result.Data2, &result.Data3,\n               &result.Data4[0], &result.Data4[1], &result.Data4[2], &result.Data4[3],\n               &result.Data4[4], &result.Data4[5], &result.Data4[6], &result.Data4[7]);\n        return result;\n    }\n\n    static inline String fmod_guid_to_string(const FMOD_GUID& guid) {\n        char result[39];\n        snprintf(result, sizeof(result), \"{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}\",\n                 guid.Data1, guid.Data2, guid.Data3,\n                 guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],\n                 guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);\n        return result;\n    }\n\n    static inline uint64_t fmod_parameter_id_to_ulong(const FMOD_STUDIO_PARAMETER_ID& parameter_id) {\n        const unsigned int first_id_part {parameter_id.data1};\n        return (static_cast<uint64_t>(first_id_part) << 32) | static_cast<uint64_t>(parameter_id.data2);\n    }\n\n    static inline FMOD_STUDIO_PARAMETER_ID ulong_to_fmod_parameter_id(uint64_t converted) {\n        FMOD_STUDIO_PARAMETER_ID paramId;\n        paramId.data2 = static_cast<unsigned int>(converted & 0xFFFFFFFF);\n        paramId.data1 = static_cast<unsigned int>((converted >> 32) & 0xFFFFFFFF);\n        return paramId;\n    }\n\n    static inline bool equals(const FMOD_GUID& first, const FMOD_GUID& second) {\n        return first.Data1 == second.Data1\n        && first.Data2 == second.Data2\n        && first.Data3 == second.Data3\n        && first.Data4[0] == second.Data4[0]\n        && first.Data4[1] == second.Data4[1]\n        && first.Data4[2] == second.Data4[2]\n        && first.Data4[3] == second.Data4[3]\n        && first.Data4[4] == second.Data4[4]\n        && first.Data4[5] == second.Data4[5]\n        && first.Data4[6] == second.Data4[6]\n        && first.Data4[7] == second.Data4[7];\n    }\n}// namespace godot\n\n#endif// GODOTFMOD_COMMON_H\n"
  },
  {
    "path": "src/helpers/constants.h",
    "content": "#ifndef GODOTFMOD_BIND_CONSTANTS_H\n#define GODOTFMOD_BIND_CONSTANTS_H\n\n#include <fmod_common.h>\n\n#include <core/class_db.hpp>\n\n#define REGISTER_ALL_CONSTANTS                                         \\\n    BIND_CONSTANT(FMOD_INIT_3D_RIGHTHANDED)                            \\\n    BIND_CONSTANT(FMOD_INIT_CHANNEL_DISTANCEFILTER)                    \\\n    BIND_CONSTANT(FMOD_INIT_CHANNEL_LOWPASS)                           \\\n    BIND_CONSTANT(FMOD_INIT_GEOMETRY_USECLOSEST)                       \\\n    BIND_CONSTANT(FMOD_INIT_MIX_FROM_UPDATE)                           \\\n    BIND_CONSTANT(FMOD_INIT_NORMAL)                                    \\\n    BIND_CONSTANT(FMOD_INIT_PREFER_DOLBY_DOWNMIX)                      \\\n    BIND_CONSTANT(FMOD_INIT_PROFILE_ENABLE)                            \\\n    BIND_CONSTANT(FMOD_INIT_PROFILE_METER_ALL)                         \\\n    BIND_CONSTANT(FMOD_INIT_STREAM_FROM_UPDATE)                        \\\n    BIND_CONSTANT(FMOD_INIT_THREAD_UNSAFE)                             \\\n    BIND_CONSTANT(FMOD_INIT_VOL0_BECOMES_VIRTUAL)                      \\\n                                                                       \\\n    BIND_CONSTANT(FMOD_STUDIO_INIT_NORMAL)                             \\\n    BIND_CONSTANT(FMOD_STUDIO_INIT_LIVEUPDATE)                         \\\n    BIND_CONSTANT(FMOD_STUDIO_INIT_ALLOW_MISSING_PLUGINS)              \\\n    BIND_CONSTANT(FMOD_STUDIO_INIT_SYNCHRONOUS_UPDATE)                 \\\n    BIND_CONSTANT(FMOD_STUDIO_INIT_DEFERRED_CALLBACKS)                 \\\n    BIND_CONSTANT(FMOD_STUDIO_INIT_LOAD_FROM_UPDATE)                   \\\n                                                                       \\\n    BIND_CONSTANT(FMOD_SPEAKERMODE_5POINT1)                            \\\n    BIND_CONSTANT(FMOD_SPEAKERMODE_7POINT1)                            \\\n    BIND_CONSTANT(FMOD_SPEAKERMODE_7POINT1POINT4)                      \\\n    BIND_CONSTANT(FMOD_SPEAKERMODE_DEFAULT)                            \\\n    BIND_CONSTANT(FMOD_SPEAKERMODE_MAX)                                \\\n    BIND_CONSTANT(FMOD_SPEAKERMODE_MONO)                               \\\n    BIND_CONSTANT(FMOD_SPEAKERMODE_QUAD)                               \\\n    BIND_CONSTANT(FMOD_SPEAKERMODE_RAW)                                \\\n    BIND_CONSTANT(FMOD_SPEAKERMODE_STEREO)                             \\\n    BIND_CONSTANT(FMOD_SPEAKERMODE_SURROUND)                           \\\n                                                                       \\\n    BIND_CONSTANT(FMOD_STUDIO_LOAD_BANK_NORMAL)                        \\\n    BIND_CONSTANT(FMOD_STUDIO_LOAD_BANK_NONBLOCKING)                   \\\n    BIND_CONSTANT(FMOD_STUDIO_LOAD_BANK_DECOMPRESS_SAMPLES)            \\\n                                                                       \\\n    BIND_CONSTANT(FMOD_2D)                                             \\\n    BIND_CONSTANT(FMOD_3D)                                             \\\n    BIND_CONSTANT(FMOD_3D_CUSTOMROLLOFF)                               \\\n    BIND_CONSTANT(FMOD_3D_HEADRELATIVE)                                \\\n    BIND_CONSTANT(FMOD_3D_IGNOREGEOMETRY)                              \\\n    BIND_CONSTANT(FMOD_3D_INVERSEROLLOFF)                              \\\n    BIND_CONSTANT(FMOD_3D_INVERSETAPEREDROLLOFF)                       \\\n    BIND_CONSTANT(FMOD_3D_LINEARROLLOFF)                               \\\n    BIND_CONSTANT(FMOD_3D_LINEARSQUAREROLLOFF)                         \\\n    BIND_CONSTANT(FMOD_3D_WORLDRELATIVE)                               \\\n    BIND_CONSTANT(FMOD_ACCURATETIME)                                   \\\n    BIND_CONSTANT(FMOD_CREATECOMPRESSEDSAMPLE)                         \\\n    BIND_CONSTANT(FMOD_CREATESAMPLE)                                   \\\n    BIND_CONSTANT(FMOD_CREATESTREAM)                                   \\\n    BIND_CONSTANT(FMOD_DEFAULT)                                        \\\n    BIND_CONSTANT(FMOD_IGNORETAGS)                                     \\\n    BIND_CONSTANT(FMOD_LOOP_BIDI)                                      \\\n    BIND_CONSTANT(FMOD_LOOP_NORMAL)                                    \\\n    BIND_CONSTANT(FMOD_LOOP_OFF)                                       \\\n    BIND_CONSTANT(FMOD_LOWMEM)                                         \\\n    BIND_CONSTANT(FMOD_MPEGSEARCH)                                     \\\n    BIND_CONSTANT(FMOD_NONBLOCKING)                                    \\\n    BIND_CONSTANT(FMOD_OPENMEMORY)                                     \\\n    BIND_CONSTANT(FMOD_OPENMEMORY_POINT)                               \\\n    BIND_CONSTANT(FMOD_OPENONLY)                                       \\\n    BIND_CONSTANT(FMOD_OPENRAW)                                        \\\n    BIND_CONSTANT(FMOD_OPENUSER)                                       \\\n    BIND_CONSTANT(FMOD_UNIQUE)                                         \\\n    BIND_CONSTANT(FMOD_VIRTUAL_PLAYFROMSTART)                          \\\n                                                                       \\\n    BIND_CONSTANT(FMOD_STUDIO_STOP_ALLOWFADEOUT)                       \\\n    BIND_CONSTANT(FMOD_STUDIO_STOP_IMMEDIATE)                          \\\n    BIND_CONSTANT(FMOD_STUDIO_STOP_FORCEINT)                           \\\n                                                                       \\\n    BIND_CONSTANT(FMOD_STUDIO_SYSTEM_CALLBACK_PREUPDATE)               \\\n    BIND_CONSTANT(FMOD_STUDIO_SYSTEM_CALLBACK_POSTUPDATE)              \\\n    BIND_CONSTANT(FMOD_STUDIO_SYSTEM_CALLBACK_BANK_UNLOAD)             \\\n    BIND_CONSTANT(FMOD_STUDIO_SYSTEM_CALLBACK_LIVEUPDATE_CONNECTED)    \\\n    BIND_CONSTANT(FMOD_STUDIO_SYSTEM_CALLBACK_LIVEUPDATE_DISCONNECTED) \\\n    BIND_CONSTANT(FMOD_STUDIO_SYSTEM_CALLBACK_ALL)                     \\\n                                                                       \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_CREATED)                  \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_DESTROYED)                \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_STARTING)                 \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_STARTED)                  \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_RESTARTED)                \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_STOPPED)                  \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_START_FAILED)             \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND)  \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND) \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_PLUGIN_CREATED)           \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_PLUGIN_DESTROYED)         \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_MARKER)          \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_BEAT)            \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED)             \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_SOUND_STOPPED)            \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_REAL_TO_VIRTUAL)          \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_VIRTUAL_TO_REAL)          \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_START_EVENT_COMMAND)      \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_NESTED_TIMELINE_BEAT)     \\\n    BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_ALL)                      \\\n                                                                       \\\n    BIND_CONSTANT(FMOD_STUDIO_LOADING_STATE_UNLOADING)                 \\\n    BIND_CONSTANT(FMOD_STUDIO_LOADING_STATE_UNLOADED)                  \\\n    BIND_CONSTANT(FMOD_STUDIO_LOADING_STATE_LOADING)                   \\\n    BIND_CONSTANT(FMOD_STUDIO_LOADING_STATE_LOADED)                    \\\n    BIND_CONSTANT(FMOD_STUDIO_LOADING_STATE_ERROR)                     \\\n    BIND_CONSTANT(FMOD_STUDIO_LOADING_STATE_FORCEINT)                  \\\n                                                                       \\\n    BIND_CONSTANT(FMOD_STUDIO_PLAYBACK_PLAYING)                        \\\n    BIND_CONSTANT(FMOD_STUDIO_PLAYBACK_SUSTAINING)                     \\\n    BIND_CONSTANT(FMOD_STUDIO_PLAYBACK_STOPPED)                        \\\n    BIND_CONSTANT(FMOD_STUDIO_PLAYBACK_STARTING)                       \\\n    BIND_CONSTANT(FMOD_STUDIO_PLAYBACK_STOPPING)                       \\\n    BIND_CONSTANT(FMOD_STUDIO_PLAYBACK_FORCEINT)\n\n#endif// GODOTFMOD_BIND_CONSTANTS_H\n"
  },
  {
    "path": "src/helpers/current_function.h",
    "content": "#ifndef BOOST_CURRENT_FUNCTION_HPP_INCLUDED\n#define BOOST_CURRENT_FUNCTION_HPP_INCLUDED\n\n// MS compatible compilers support #pragma once\n\n#if defined(_MSC_VER) && (_MSC_VER >= 1020)\n    #pragma once\n#endif\n\n//\n//  boost/current_function.hpp - BOOST_CURRENT_FUNCTION\n//\n//  Copyright (c) 2002 Peter Dimov and Multi Media Ltd.\n//\n// Distributed under the Boost Software License, Version 1.0. (See\n// accompanying file LICENSE_1_0.txt or copy at\n// http://www.boost.org/LICENSE_1_0.txt)\n//\n//  http://www.boost.org/libs/utility/current_function.html\n//\n\nnamespace boost {\n\n    namespace detail {\n\n        inline void current_function_helper() {\n#if defined(__GNUC__) || (defined(__MWERKS__) && (__MWERKS__ >= 0x3000)) || (defined(__ICC) && (__ICC >= 600))\n\n    #define BOOST_CURRENT_FUNCTION __PRETTY_FUNCTION__\n\n#elif defined(__DMC__) && (__DMC__ >= 0x810)\n\n    #define BOOST_CURRENT_FUNCTION __PRETTY_FUNCTION__\n\n#elif defined(__FUNCSIG__)\n\n    #define BOOST_CURRENT_FUNCTION __FUNCSIG__\n\n#elif (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 600)) || (defined(__IBMCPP__) && (__IBMCPP__ >= 500))\n\n    #define BOOST_CURRENT_FUNCTION __FUNCTION__\n\n#elif defined(__BORLANDC__) && (__BORLANDC__ >= 0x550)\n\n    #define BOOST_CURRENT_FUNCTION __FUNC__\n\n#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901)\n\n    #define BOOST_CURRENT_FUNCTION __func__\n\n#else\n\n    #define BOOST_CURRENT_FUNCTION \"(unknown)\"\n\n#endif\n        }\n\n    }// namespace detail\n\n}// namespace boost\n\n#endif// #ifndef BOOST_CURRENT_FUNCTION_HPP_INCLUDED\n"
  },
  {
    "path": "src/helpers/files.h",
    "content": "#ifndef GODOTFMOD_FILES_H\n#define GODOTFMOD_FILES_H\n\n#include <classes/dir_access.hpp>\n#include <variant/packed_string_array.hpp>\n\nnamespace godot {\n    static void list_files_in_folder(PackedStringArray& result, const String& folder, const String& extension = \"\",\n                                     const PackedStringArray& excluded_folders = PackedStringArray()) {\n        for (const String& excluded : excluded_folders) {\n            if (folder == excluded) {\n                return;\n            }\n        }\n\n        Ref<DirAccess> folder_access {DirAccess::open(folder)};\n\n        if (folder_access.is_null()) {\n            return;\n        }\n\n        folder_access->list_dir_begin();\n        String current_file{folder_access->get_next()};\n        while (!current_file.is_empty()) {\n            if (current_file == \"..\" || current_file == \".\") {\n                current_file = folder_access->get_next();\n                continue;\n            }\n\n            String current_file_path{vformat(\"%s/%s\", folder, current_file)};\n            if (folder_access->current_is_dir()) {\n                list_files_in_folder(result, current_file_path, extension, excluded_folders);\n            } else if (current_file.ends_with(extension)) {\n                result.push_back(current_file_path);\n            }\n\n            current_file = folder_access->get_next();\n        }\n        folder_access->list_dir_end();\n    }\n}\n\n#endif// GODOTFMOD_FILES_H\n"
  },
  {
    "path": "src/helpers/maths.h",
    "content": "#ifndef GODOTFMOD_MATHS_H\n#define GODOTFMOD_MATHS_H\n\n#include \"fmod_common.h\"\n#include \"variant/dictionary.hpp\"\n#include \"variant/transform2d.hpp\"\n#include \"variant/transform3d.hpp\"\n#include \"variant/vector2.hpp\"\n#include \"variant/vector3.hpp\"\n\nnamespace godot {\n\n    static inline FMOD_VECTOR get_fmod_vector_from_3d(const Vector3& vec) {\n        FMOD_VECTOR fv;\n        fv.x = vec.x;\n        fv.y = vec.y;\n        fv.z = vec.z;\n        return fv;\n    }\n\n    static inline FMOD_3D_ATTRIBUTES get_3d_attributes(const FMOD_VECTOR& pos, const FMOD_VECTOR& up, const FMOD_VECTOR& forward, const FMOD_VECTOR& vel) {\n        FMOD_3D_ATTRIBUTES f3d;\n        f3d.forward = forward;\n        f3d.position = pos;\n        f3d.up = up;\n        f3d.velocity = vel;\n        return f3d;\n    }\n\n    static inline FMOD_3D_ATTRIBUTES get_3d_attributes_from_transform3d(const Transform3D& transform, const Vector3& velocity, const float distanceScale) {\n        Vector3 pos = transform.get_origin() / distanceScale;\n        Vector3 up = transform.get_basis().get_column(1).normalized();;\n        Vector3 forward = -transform.get_basis().get_column(2).normalized();;\n        return get_3d_attributes(get_fmod_vector_from_3d(pos), get_fmod_vector_from_3d(up), get_fmod_vector_from_3d(forward), get_fmod_vector_from_3d(velocity));\n    }\n\n    static inline FMOD_3D_ATTRIBUTES get_3d_attributes_from_transform3d(const Transform3D& transform, const float distanceScale) {\n        Vector3 pos = transform.get_origin() / distanceScale;\n        Vector3 up = transform.get_basis().get_column(1).normalized();\n        Vector3 forward = -transform.get_basis().get_column(2).normalized();\n        Vector3 vel(0, 0, 0);\n        return get_3d_attributes(get_fmod_vector_from_3d(pos), get_fmod_vector_from_3d(up), get_fmod_vector_from_3d(forward), get_fmod_vector_from_3d(vel));\n    }\n\n    static inline FMOD_3D_ATTRIBUTES get_3d_attributes_from_transform2d(const Transform2D& transform, const Vector2& velocity, const float distanceScale) {\n        Vector2 posVector = transform.get_origin() / distanceScale;\n        Vector3 pos = Vector3(-posVector.x, 0.0f, posVector.y);\n        Vector3 up = Vector3(0, 1, 0).normalized();\n        Vector3 forward = Vector3(-transform.columns[1].x, 0, transform.columns[1].y).normalized();\n        Vector3 vel(-velocity.x, 0, velocity.y);\n        return get_3d_attributes(get_fmod_vector_from_3d(pos), get_fmod_vector_from_3d(up), get_fmod_vector_from_3d(forward), get_fmod_vector_from_3d(vel));\n    }\n\n    static inline FMOD_3D_ATTRIBUTES get_3d_attributes_from_transform2d(const Transform2D& transform, const float distanceScale) {\n        Vector2 posVector = transform.get_origin() / distanceScale;\n        Vector3 pos(-posVector.x, 0.0f, posVector.y);\n        Vector3 up(0, 1, 0);\n        Vector3 forward = Vector3(-transform.columns[1].x, 0, transform.columns[1].y).normalized();\n        Vector3 vel(0, 0, 0);\n        const FMOD_VECTOR& posFmodVector = get_fmod_vector_from_3d(pos);\n        return get_3d_attributes(posFmodVector, get_fmod_vector_from_3d(up), get_fmod_vector_from_3d(forward), get_fmod_vector_from_3d(vel));\n    }\n\n    static inline Transform3D get_transform3d_from_3d_attributes(FMOD_3D_ATTRIBUTES& attr, const float distanceScale) {\n        Transform3D transform;\n        transform.origin = Vector3(attr.position.x, attr.position.y, attr.position.z) * distanceScale;\n        const Vector3& upVector = Vector3(attr.up.x, attr.up.y, attr.up.z);\n        transform.basis.set_column(1, upVector);\n        const Vector3& forwardVector = Vector3(attr.forward.x, attr.forward.y, attr.forward.z);\n        transform.basis.set_column(2, forwardVector);\n        transform.basis.set_column(0, upVector.cross(forwardVector));\n        return transform;\n    }\n\n    static inline Transform2D get_transform2d_from_3d_attributes(FMOD_3D_ATTRIBUTES& attr, const float distanceScale) {\n        Transform2D transform;\n        transform.set_origin(Vector2(-attr.position.x, attr.position.z) * distanceScale);\n        const Vector2& forward = Vector2(-attr.forward.x, attr.forward.z);\n        transform.columns[1] = forward;\n        transform.columns[0] = Vector2(forward.y, forward.x);\n        return transform;\n    }\n\n    static inline Vector3 get_velocity3d_from_3d_attributes(FMOD_3D_ATTRIBUTES& attr, const float distanceScale) {\n        return {attr.velocity.x, attr.velocity.y, attr.velocity.z};\n    }\n\n    static inline Vector2 get_velocity2d_from_3d_attributes(FMOD_3D_ATTRIBUTES& attr, const float distanceScale) {\n        return {-attr.velocity.x, attr.velocity.z};\n    }\n\n}// namespace godot\n\n#endif// GODOTFMOD_MATHS_H\n"
  },
  {
    "path": "src/nodes/fmod_bank_loader.cpp",
    "content": "#include \"fmod_bank_loader.h\"\n\n#include \"fmod_server.h\"\n#include \"fmod_string_names.h\"\n\n#include <classes/engine.hpp>\n\nusing namespace godot;\n\nvoid FmodBankLoader::_enter_tree() {\n#ifdef TOOLS_ENABLED\n    if (Engine::get_singleton()->is_editor_hint()) {\n        return;\n    }\n#endif\n    for (int i = 0; i < bank_paths.size(); ++i) {\n        bank.append(\n          FmodServer::get_singleton()->load_bank(bank_paths[i], FMOD_STUDIO_LOAD_BANK_NORMAL)\n        );\n    }\n}\n\nvoid FmodBankLoader::set_bank_paths(const Array& p_paths) {\n    bank_paths = p_paths;\n}\n\nconst Array& FmodBankLoader::get_bank_paths() const {\n    return bank_paths;\n}\n\nbool FmodBankLoader::_property_can_revert(const StringName& p_property) const {\n    if (p_property == FmodStringNames::get_instance()->bank_path_property_name) {\n        return true;\n    }\n\n    return false;\n}\n\nbool FmodBankLoader::_property_get_revert(const StringName& p_property, Variant& result) const {\n    if (p_property != FmodStringNames::get_instance()->bank_path_property_name) {\n        return false;\n    }\n\n    result = Array();\n    return true;\n}\n\nvoid godot::FmodBankLoader::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"set_bank_paths\", \"p_paths\"), &FmodBankLoader::set_bank_paths);\n    ClassDB::bind_method(D_METHOD(\"get_bank_paths\"), &FmodBankLoader::get_bank_paths);\n\n    ADD_PROPERTY(\n      PropertyInfo(\n        Variant::ARRAY,\n        FmodStringNames::get_instance()->bank_path_property_name,\n        PROPERTY_HINT_NONE,\n        \"\",\n        PROPERTY_USAGE_DEFAULT\n      ),\n      \"set_bank_paths\",\n      \"get_bank_paths\"\n    );\n}"
  },
  {
    "path": "src/nodes/fmod_bank_loader.h",
    "content": "#ifndef GODOTFMOD_FMOD_BANK_LOADER_H\n#define GODOTFMOD_FMOD_BANK_LOADER_H\n\n#include \"classes/node.hpp\"\n#include \"studio/fmod_bank.h\"\n\nnamespace godot {\n    class FmodBankLoader : public Node {\n        GDCLASS(FmodBankLoader, Node)\n\n    public:\n        virtual void _enter_tree() override;\n\n        void set_bank_paths(const Array& p_paths);\n        const Array& get_bank_paths() const;\n\n        bool _property_can_revert(const StringName& p_property) const;\n        bool _property_get_revert(const StringName& p_property, Variant& result) const;\n\n    private:\n        Vector<Ref<FmodBank>> bank;\n        Array bank_paths;\n\n    public:\n        static void _bind_methods();\n    };\n}// namespace godot\n\n#endif// GODOTFMOD_FMOD_BANK_LOADER_H\n"
  },
  {
    "path": "src/nodes/fmod_event_emitter.h",
    "content": "#ifndef GODOTFMOD_FMOD_EVENT_EMITTER_H\n#define GODOTFMOD_FMOD_EVENT_EMITTER_H\n\n#include \"classes/object.hpp\"\n\n#include <constants.h>\n#include <fmod_server.h>\n#include <fmod_string_names.h>\n\n#include <classes/engine.hpp>\n#include <classes/project_settings.hpp>\n\nstatic constexpr const char* BEAT_SIGNAL_STRING = \"timeline_beat\";\nstatic constexpr const char* MARKER_SIGNAL_STRING = \"timeline_marker\";\nstatic constexpr const char* START_FAILED_SIGNAL_STRING = \"start_failed\";\nstatic constexpr const char* STARTED_SIGNAL_STRING = \"started\";\nstatic constexpr const char* RESTARTED_SIGNAL_STRING = \"restarted\";\nstatic constexpr const char* STOPPED_SIGNAL_STRING = \"stopped\";\n\nnamespace godot {\n\n    template<class Derived, class NodeType>\n    class FmodEventEmitter : public NodeType {\n        struct Parameter : public FmodServer::ParameterValue {\n            String name;\n            uint64_t id;\n            PackedStringArray labels;\n\n            bool operator==(const Parameter& parameter) const { return id == parameter.id; }\n        };\n\n        mutable Ref<FmodEventDescription> _event_description;\n        Ref<FmodEvent> _event;\n\n        String _event_name;\n        String _programmer_callback_sound_key;\n        LocalVector<Parameter> _parameters;\n        FMOD_GUID _event_guid;\n        float _volume = 1.0;\n        bool _attached = true;\n        bool _autoplay = false;\n        bool _auto_release = false;\n        bool _allow_fadeout = true;\n        bool _preload_event = true;\n\n        void ready();\n        void process();\n        void exit_tree();\n\n    public:\n        void _notification(int p_what);\n\n        void play(bool restart_if_playing = true);\n        void stop();\n        void play_one_shot();\n        Variant get_parameter(const String& p_name) const;\n        void set_parameter(const String& p_name, const Variant& p_property);\n        Variant get_parameter_by_id(uint64_t p_id) const;\n        void set_parameter_by_id(uint64_t p_id, const Variant& p_property);\n        void set_paused(bool p_is_paused);\n        const Ref<FmodEvent>& get_event() const;\n        bool is_paused();\n        void set_event_name(const String& name);\n        String get_event_name() const;\n        void set_event_guid(const String& guid);\n        String get_event_guid() const;\n        void set_attached(bool attached);\n        bool is_attached() const;\n        void set_autoplay(bool autoplay);\n        bool is_autoplay() const;\n        void set_auto_release(bool auto_release);\n        bool is_auto_release() const;\n        void set_allow_fadeout(bool allow_fadeout);\n        bool is_allow_fadeout() const;\n        void set_preload_event(bool preload_event);\n        bool is_preload_event() const;\n        void set_volume(float volume);\n        float get_volume() const;\n\n        void set_programmer_callback(const String& p_programmers_callback_sound_key);\n\n#ifdef TOOLS_ENABLED\n        void tool_remove_all_parameters();\n        void tool_remove_parameter(uint64_t parameter_id);\n#endif\n\n        static const StringName& get_class_static();\n\n    protected:\n        void _emit_callbacks(const Dictionary& dict, int type) const;\n        bool _set(const StringName& p_name, const Variant& p_property);\n        bool _get(const StringName& p_name, Variant& r_property) const;\n        bool _property_can_revert(const StringName& p_name) const;\n        bool _property_get_revert(const StringName& p_name, Variant& r_property) const;\n        void _get_property_list(List<PropertyInfo>* p_list) const;\n        static void _bind_methods();\n\n    private:\n        template<bool is_one_shot> void _play(bool should_start_event);\n        void set_space_attribute(const Ref<FmodEvent>& p_event) const;\n        void _set_parameter_value(Parameter* parameter, const Variant& p_property);\n        void _apply_parameters(const Ref<FmodEvent>& p_event);\n        void _apply_parameters();\n        void free();\n        void _load_event_description_if_needed() const;\n        void _load_event();\n        void _unload_event();\n        Ref<FmodEvent> _create_event();\n        Parameter* _find_parameter(const String& p_name) const;\n        Parameter* _find_parameter(uint64_t p_id) const;\n        void _stop_and_restart_if_autoplay();\n        Ref<FmodParameterDescription> _get_parameter_description(const Parameter& parameter) const;\n\n\n        static bool _should_load_by_event_name();\n    };\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::set_space_attribute(const Ref<FmodEvent>& p_event) const {\n        static_cast<const Derived*>(this)->set_space_attribute_impl(p_event);\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::free() {\n        static_cast<Derived*>(this)->free_impl();\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::ready() {\n#ifdef TOOLS_ENABLED\n        // ensure we only run FMOD when the game is running!\n        if (Engine::get_singleton()->is_editor_hint()) { return; }\n#endif\n\n        if (_autoplay) {\n            play();\n        } else if (_preload_event) {\n            // No need to preload if autoplay is on because event is loaded anyway.\n            _load_event();\n        }\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::process() {\n#ifdef TOOLS_ENABLED\n        if (Engine::get_singleton()->is_editor_hint()) { return; }\n#endif\n\n        if (_event.is_null()) {\n            // No event loaded, nothing to do here\n            return;\n        }\n\n        if (!_event->is_valid()) {\n            // Event was loaded and is done playing.\n            if (_auto_release) {\n                free();\n                return;\n            }\n            if (_autoplay) {\n                play();\n            }\n        }\n\n        if (_attached && _event->is_valid()) { set_space_attribute(_event); }\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::exit_tree() {\n#ifdef TOOLS_ENABLED\n        if (Engine::get_singleton()->is_editor_hint()) { return; }\n#endif\n\n        if (_event.is_null()) {\n            // No event loaded, nothing to stop or free.\n            return;\n        }\n\n        if (_event->is_valid()) {\n            _event->release();\n            stop();\n        }\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::_notification(int p_what) {\n#ifdef TOOLS_ENABLED\n        // ensure we only run FMOD when the game is running!\n        if (Engine::get_singleton()->is_editor_hint()) { return; }\n#endif\n        switch (p_what) {\n            case Node::NOTIFICATION_PAUSED:\n                set_paused(true);\n                break;\n            case Node::NOTIFICATION_UNPAUSED:\n                if (is_paused()) { set_paused(false); }\n                break;\n            case Node::NOTIFICATION_READY:\n                ready();\n                break;\n            case Node::NOTIFICATION_PROCESS:\n                process();\n                break;\n            case Node::NOTIFICATION_EXIT_TREE:\n                exit_tree();\n                break;\n            default:\n                break;\n        }\n    }\n\n    template<class Derived, class NodeType>\n    bool FmodEventEmitter<Derived, NodeType>::is_paused() {\n        if (_event.is_null() || !_event->is_valid()) { return false; }\n        return _event->get_paused();\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::play(bool restart_if_playing) {\n        _play<false>(restart_if_playing);\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::stop() {\n        if (_event.is_null() || !_event->is_valid()) { return; }\n        if (_allow_fadeout) {\n            _event->stop(FMOD_STUDIO_STOP_ALLOWFADEOUT);\n\n            return;\n        }\n        _event->stop(FMOD_STUDIO_STOP_IMMEDIATE);\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::play_one_shot() {\n        _play<true>(true);\n    }\n\n    template<class Derived, class NodeType>\n    template<bool is_one_shot>\n    void FmodEventEmitter<Derived, NodeType>::_play(bool should_start_event) {\n        Ref<FmodEvent> event;\n        if (is_one_shot) {\n            event = _create_event();\n        } else {\n            if (_event.is_null() || !_event->is_valid()) { _load_event(); }\n\n            event = _event;\n        }\n\n        if (event.is_null()) {\n            // No event loaded, nothing to do here\n            return;\n        }\n\n        event->set_volume(_volume);\n        _apply_parameters(event);\n\n        set_space_attribute(event);\n        if (!_programmer_callback_sound_key.is_empty()) {\n            event->set_programmer_callback(_programmer_callback_sound_key);\n        }\n\n        if (!should_start_event && event->get_playback_state() != FMOD_STUDIO_PLAYBACK_STOPPED) {\n            return;\n        }\n\n        event->start();\n        event->release();\n    }\n\n    template<class Derived, class NodeType>\n    Variant FmodEventEmitter<Derived, NodeType>::get_parameter(const String& p_name) const {\n        if (Parameter* parameter {_find_parameter(p_name)}) {\n            return parameter->value;\n        }\n\n        return Variant();\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::set_parameter(const String& p_name, const Variant& p_property) {\n        Parameter* parameter {_find_parameter(p_name)};\n\n        if (!parameter) {\n            Parameter param;\n\n            _parameters.push_back(param);\n            parameter = &_parameters[_parameters.size() - 1];\n            parameter->name = p_name;\n            parameter->identifier.name = &parameter->name;\n            parameter->should_load_by_id = false;\n        }\n\n        _set_parameter_value(parameter, p_property);\n    }\n\n    template<class Derived, class NodeType>\n    Variant FmodEventEmitter<Derived, NodeType>::get_parameter_by_id(uint64_t p_id) const {\n        if (Parameter* parameter {_find_parameter(p_id)}) {\n            return parameter->value;\n        }\n\n        return Variant();\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::set_parameter_by_id(const uint64_t p_id, const Variant& p_property) {\n        Parameter* parameter {_find_parameter(p_id)};\n\n        if (!parameter) {\n            Parameter param;\n            _parameters.push_back(param);\n            parameter = &_parameters[_parameters.size() - 1];\n            parameter->id = p_id;\n            parameter->should_load_by_id = true;\n            parameter->identifier.id = p_id;\n        }\n\n        _set_parameter_value(parameter, p_property);\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::set_paused(bool p_is_paused) {\n        if (_event.is_null() || !_event->is_valid()) { return; }\n        _event->set_paused(p_is_paused);\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::_load_event_description_if_needed() const {\n        if (!_event_description.is_null()) {\n            return;\n        }\n\n        if (_should_load_by_event_name()) {\n#ifdef DEBUG_ENABLED\n            if (FmodServer::get_singleton()->check_event_path(_event_name)) {\n#endif\n                _event_description = FmodServer::get_singleton()->get_event(_event_name);\n#ifdef DEBUG_ENABLED\n            } else {\n                GODOT_LOG_ERROR(vformat(\"Cannot find event with path %s, will try with guid\", _event_name));\n                GODOT_LOG_ERROR(\"You should fix this before releasing your game, check event exists and fallback is \"\n                                \"only a debug feature\");\n                if (FmodServer::get_singleton()->check_event_guid_internal(_event_guid)) {\n                    _event_description = FmodServer::get_singleton()->get_event_from_guid_internal(_event_guid);\n                } else {\n                    GODOT_LOG_ERROR(\n                      vformat(\"Cannot find event with guid %s and path %s. Please set right data from editor.\", get_event_guid(), _event_name)\n                    );\n                    GODOT_LOG_ERROR(\"You should fix this before releasing your game, check event exists and fallback \"\n                                    \"is only a debug feature\");\n                }\n            }\n#endif\n        } else {\n#ifdef DEBUG_ENABLED\n            if (FmodServer::get_singleton()->check_event_guid_internal(_event_guid)) {\n#endif\n                _event_description = FmodServer::get_singleton()->get_event_from_guid_internal(_event_guid);\n#ifdef DEBUG_ENABLED\n            } else {\n                GODOT_LOG_ERROR(vformat(\"Cannot find event with guid %s, will try with guid\", get_event_guid()));\n                GODOT_LOG_ERROR(\"You should fix this before releasing your game, check event exists and fallback is \"\n                                \"only a debug feature\");\n                if (FmodServer::get_singleton()->check_event_path(_event_name)) {\n                    _event_description = FmodServer::get_singleton()->get_event(_event_name);\n                } else {\n                    GODOT_LOG_ERROR(\n                      vformat(\"Cannot find event with guid %s and path %s. Please set right data from editor.\", get_event_guid(), _event_name)\n                    );\n                    GODOT_LOG_ERROR(\"You should fix this before releasing your game, check event exists and fallback \"\n                                    \"is only a debug feature\");\n                }\n            }\n#endif\n        }\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::_load_event() {\n        _event = _create_event();\n\n        if (_event.is_null()) {\n            return;\n        }\n\n        _event->set_callback(Callable(this, \"_emit_callbacks\"), FMOD_STUDIO_EVENT_CALLBACK_ALL);\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::_unload_event() {\n        _event_description = Ref<FmodEventDescription>();\n        _event = Ref<FmodEvent>();\n    }\n\n    template<class Derived, class NodeType>\n    Ref<FmodEvent> FmodEventEmitter<Derived, NodeType>::_create_event() {\n        _load_event_description_if_needed();\n\n        if (_event_description.is_null()) {\n            return {};\n        }\n\n        return FmodServer::get_singleton()->create_event_instance_from_description(_event_description);\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::_set_parameter_value(FmodEventEmitter::Parameter* parameter,\n                                                                   const Variant& p_property) {\n        parameter->value = p_property;\n\n        if (_event.is_null() || !_event->is_valid()) { return; }\n\n#ifdef TOOLS_ENABLED\n        if (!Engine::get_singleton()->is_editor_hint()) {\n#endif\n            _apply_parameters();\n#ifdef TOOLS_ENABLED\n        }\n#endif\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::_apply_parameters(const Ref<FmodEvent>& p_event) {\n        if (p_event.is_null() || !p_event->is_valid()) { return; }\n        FmodServer::get_singleton()->apply_parameter_list_to_event(p_event, _parameters);\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::_apply_parameters() {\n        _apply_parameters(_event);\n    }\n\n    template<class Derived, class NodeType>\n    const Ref<FmodEvent>& FmodEventEmitter<Derived, NodeType>::get_event() const {\n        return _event;\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::set_event_name(const String& name) {\n        FMOD_GUID event_guid {FmodServer::get_singleton()->get_event_guid_internal(name)};\n\n        if (equals(_event_guid, event_guid)) {\n            return;\n        }\n\n        _event_name = name;\n        _event_guid = event_guid;\n\n        _stop_and_restart_if_autoplay();\n    }\n\n    template<class Derived, class NodeType>\n    String FmodEventEmitter<Derived, NodeType>::get_event_name() const {\n        return _event_name;\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::set_event_guid(const String& guid) {\n        FMOD_GUID event_guid {string_to_fmod_guid(guid.utf8().get_data())};\n\n        if (equals(_event_guid, event_guid)) {\n            return;\n        }\n\n        _event_guid = event_guid;\n        _event_name = FmodServer::get_singleton()->get_event_path_internal(_event_guid);\n\n        _stop_and_restart_if_autoplay();\n    }\n\n    template<class Derived, class NodeType>\n    String FmodEventEmitter<Derived, NodeType>::get_event_guid() const {\n        return fmod_guid_to_string(_event_guid);\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::_stop_and_restart_if_autoplay() {\n        if (!reinterpret_cast<Derived*>(this)->is_node_ready()) return;\n\n        stop();\n\n        _unload_event();\n\n        if (_preload_event) {\n            _load_event();\n        }\n\n        _parameters.clear();\n\n        if (!_autoplay) return;\n\n        play();\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::set_attached(const bool attached) {\n        _attached = attached;\n    }\n\n    template<class Derived, class NodeType>\n    bool FmodEventEmitter<Derived, NodeType>::is_attached() const {\n        return _attached;\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::set_autoplay(const bool autoplay) {\n        _autoplay = autoplay;\n    }\n\n    template<class Derived, class NodeType>\n    bool FmodEventEmitter<Derived, NodeType>::is_autoplay() const {\n        return _autoplay;\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::set_auto_release(const bool auto_release) {\n        _auto_release = auto_release;\n    }\n\n    template<class Derived, class NodeType>\n    bool FmodEventEmitter<Derived, NodeType>::is_auto_release() const {\n        return _auto_release;\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::set_allow_fadeout(const bool allow_fadeout) {\n        _allow_fadeout = allow_fadeout;\n    }\n\n    template<class Derived, class NodeType>\n    bool FmodEventEmitter<Derived, NodeType>::is_allow_fadeout() const {\n        return _allow_fadeout;\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::set_preload_event(const bool preload_event) {\n        _preload_event = preload_event;\n    }\n\n    template<class Derived, class NodeType>\n    bool FmodEventEmitter<Derived, NodeType>::is_preload_event() const {\n        return _preload_event;\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::set_volume(const float volume) {\n        _volume = volume;\n\n        if (_event.is_null() || !_event->is_valid()) { return; }\n        _event->set_volume(volume);\n    }\n\n    template<class Derived, class NodeType>\n    float FmodEventEmitter<Derived, NodeType>::get_volume() const {\n        return _volume;\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::set_programmer_callback(const String &p_programmers_callback_sound_key) {\n        _programmer_callback_sound_key = p_programmers_callback_sound_key;\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::_emit_callbacks(const Dictionary& dict, const int type) const {\n        switch (type) {\n            case FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_BEAT:\n                const_cast<Derived*>(static_cast<const Derived*>(this))->emit_signal(BEAT_SIGNAL_STRING, dict);\n                break;\n            case FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_MARKER:\n                const_cast<Derived*>(static_cast<const Derived*>(this))->emit_signal(MARKER_SIGNAL_STRING, dict);\n                break;\n            case FMOD_STUDIO_EVENT_CALLBACK_START_FAILED:\n                const_cast<Derived*>(static_cast<const Derived*>(this))->emit_signal(START_FAILED_SIGNAL_STRING);\n                break;\n            case FMOD_STUDIO_EVENT_CALLBACK_STARTED:\n                const_cast<Derived*>(static_cast<const Derived*>(this))->emit_signal(STARTED_SIGNAL_STRING);\n                break;\n            case FMOD_STUDIO_EVENT_CALLBACK_RESTARTED:\n                const_cast<Derived*>(static_cast<const Derived*>(this))->emit_signal(RESTARTED_SIGNAL_STRING);\n                break;\n            case FMOD_STUDIO_EVENT_CALLBACK_STOPPED:\n                const_cast<Derived*>(static_cast<const Derived*>(this))->emit_signal(STOPPED_SIGNAL_STRING);\n                break;\n        }\n    }\n\n    template<class Derived, class NodeType>\n    typename FmodEventEmitter<Derived, NodeType>::Parameter* FmodEventEmitter<Derived, NodeType>::_find_parameter(const String& p_name\n    ) const {\n        Parameter* parameter {nullptr};\n        for (const Parameter& item : _parameters) {\n            if (item.name != p_name) { continue; }\n            parameter = const_cast<Parameter*>(&item);\n            break;\n        }\n        return parameter;\n    }\n\n    template<class Derived, class NodeType>\n    typename FmodEventEmitter<Derived, NodeType>::Parameter* FmodEventEmitter<Derived, NodeType>::_find_parameter(uint64_t p_id) const {\n        Parameter* parameter {nullptr};\n        for (const Parameter& item : _parameters) {\n            if (item.id != p_id) { continue; }\n            parameter = const_cast<Parameter*>(&item);\n            break;\n        }\n        return parameter;\n    }\n\n    template<class Derived, class NodeType>\n    bool FmodEventEmitter<Derived, NodeType>::_should_load_by_event_name() {\n#ifndef TOOLS_ENABLED\n        static\n#endif\n          bool should_load_by_name {ProjectSettings::get_singleton()->get_setting(\n            vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodGeneralSettings::INITIALIZE_BASE_PATH, FmodGeneralSettings::SHOULD_LOAD_BY_NAME)\n          )};\n        return should_load_by_name;\n    }\n\n    template<class Derived, class NodeType>\n    bool FmodEventEmitter<Derived, NodeType>::_set(const StringName& p_name, const Variant& p_property) {\n        if (!p_name.begins_with(FmodStringNames::EVENT_PARAMETER_PREFIX_FOR_PROPERTIES)) { return false; }\n        if (p_name == FmodStringNames::get_instance()->event_parameter_prefix_for_properties) { return false; }\n\n        PackedStringArray parts {p_name.trim_prefix(vformat(\"%s/\", FmodStringNames::EVENT_PARAMETER_PREFIX_FOR_PROPERTIES)).split(\"/\")};\n\n        const String& parameter_name {parts[0]};\n\n        Parameter* parameter {_find_parameter(parameter_name)};\n\n        if (!parameter) {\n            Parameter param;\n\n            _parameters.push_back(param);\n            parameter = &_parameters[_parameters.size() - 1];\n            parameter->name = parameter_name;\n            parameter->should_load_by_id = !_should_load_by_event_name();\n        }\n\n        if (parts.size() == 1) {\n            _set_parameter_value(parameter, p_property);\n\n            return true;\n        }\n\n        const String& parameter_end = parts[1];\n        if (parameter_end == \"id\") {\n            uint64_t parameter_id = p_property.operator uint64_t();\n\n            parameter->id = parameter_id;\n\n            if (parameter->should_load_by_id) {\n                parameter->identifier.id = parameter_id;\n                return true;\n            }\n\n            parameter->identifier.name = &parameter->name;\n            return true;\n        }\n\n        if (parameter_end == \"variant_type\") {\n            parameter->variant_type = static_cast<Variant::Type>(p_property.operator int32_t());\n            return true;\n        }\n\n        if (parameter_end == \"labels\") {\n            parameter->labels = p_property;\n            return true;\n        }\n\n        return false;\n    }\n\n    template<class Derived, class NodeType>\n    bool FmodEventEmitter<Derived, NodeType>::_get(const StringName& p_name, Variant& r_property) const {\n        if (!p_name.begins_with(FmodStringNames::EVENT_PARAMETER_PREFIX_FOR_PROPERTIES)) { return false; }\n        if (p_name == FmodStringNames::get_instance()->event_parameter_prefix_for_properties) { return false; }\n\n        PackedStringArray parts {p_name.trim_prefix(vformat(\"%s/\", FmodStringNames::EVENT_PARAMETER_PREFIX_FOR_PROPERTIES)).split(\"/\")};\n\n        Parameter* parameter {_find_parameter(parts[0])};\n\n        if (!parameter) { return false; }\n\n        if (parts.size() == 1) {\n            r_property = parameter->value;\n            return true;\n        }\n\n        const String& parameter_end = parts[1];\n        if (parameter_end == \"id\") {\n            r_property = parameter->id;\n            return true;\n        }\n\n        if (parameter_end == \"variant_type\") {\n            r_property = parameter->variant_type;\n            return true;\n        }\n\n        if (parameter_end == \"labels\") {\n            r_property = parameter->labels;\n            return true;\n        }\n\n        return false;\n    }\n\n    template<class Derived, class NodeType>\n    bool FmodEventEmitter<Derived, NodeType>::_property_can_revert(const StringName& p_name) const {\n        if (!p_name.begins_with(FmodStringNames::EVENT_PARAMETER_PREFIX_FOR_PROPERTIES)) { return false; }\n        if (p_name == FmodStringNames::get_instance()->event_parameter_prefix_for_properties) { return false; }\n\n        PackedStringArray parts {p_name.trim_prefix(vformat(\"%s/\", FmodStringNames::EVENT_PARAMETER_PREFIX_FOR_PROPERTIES)).split(\"/\")};\n\n        if (parts.size() == 1) { return true; }\n\n        return false;\n    }\n\n    template<class Derived, class NodeType>\n    bool FmodEventEmitter<Derived, NodeType>::_property_get_revert(const StringName& p_name, Variant& r_property) const {\n        if (!p_name.begins_with(FmodStringNames::EVENT_PARAMETER_PREFIX_FOR_PROPERTIES)) { return false; }\n        if (p_name == FmodStringNames::get_instance()->event_parameter_prefix_for_properties) { return false; }\n\n        PackedStringArray parts {p_name.trim_prefix(vformat(\"%s/\", FmodStringNames::EVENT_PARAMETER_PREFIX_FOR_PROPERTIES)).split(\"/\")};\n\n        Parameter* parameter {_find_parameter(parts[0])};\n\n        if (!parameter) { return false; }\n\n        if (parts.size() == 1) {\n            Ref<FmodParameterDescription> desc {_get_parameter_description(*parameter)};\n            if (desc.is_null()) { return false; }\n            r_property = desc->get_default_value();\n            return true;\n        }\n\n        return false;\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::_get_property_list(List<PropertyInfo>* p_list) const {\n        p_list->push_back(\n            PropertyInfo(\n                Variant::Type::DICTIONARY,\n                FmodStringNames::get_instance()->event_parameter_prefix_for_properties,\n                PROPERTY_HINT_NONE,\n                \"\",\n                PROPERTY_USAGE_NO_EDITOR\n            )\n        );\n\n        for (const Parameter& parameter : _parameters) {\n            const String& parameter_name {parameter.name};\n\n            Ref<FmodParameterDescription> parameter_description{_get_parameter_description(parameter) };\n            if (parameter_description.is_null()) {\n                // Skip parameters that cannot be resolved (e.g., missing or stale IDs)\n                continue;\n            }\n\n            const float parameter_min_value {parameter_description->get_minimum()};\n            const float parameter_max_value {parameter_description->get_maximum()};\n            const Variant::Type parameter_variant_type {parameter.variant_type};\n\n            p_list->push_back(\n              PropertyInfo(Variant::Type::INT, vformat(\"%s/%s/id\", FmodStringNames::EVENT_PARAMETER_PREFIX_FOR_PROPERTIES, parameter_name), PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_NO_EDITOR)\n            );\n\n            if (!parameter.labels.is_empty()) {\n                p_list->push_back(\n                  PropertyInfo(\n                    parameter_variant_type,\n                    vformat(\"%s/%s\", FmodStringNames::EVENT_PARAMETER_PREFIX_FOR_PROPERTIES, parameter_name),\n                    PROPERTY_HINT_ENUM,\n                    vformat(String(\",\").join(parameter.labels))\n                  )\n                );\n                p_list->push_back(\n                  PropertyInfo(\n                    Variant::Type::PACKED_STRING_ARRAY,\n                    vformat(\"%s/%s/labels\", FmodStringNames::EVENT_PARAMETER_PREFIX_FOR_PROPERTIES, parameter_name),\n                    PROPERTY_HINT_NONE,\n                    \"\",\n                    PROPERTY_USAGE_NO_EDITOR\n                  )\n                );\n            } else {\n                p_list->push_back(\n                  PropertyInfo(\n                    parameter_variant_type,\n                    vformat(\"%s/%s\", FmodStringNames::EVENT_PARAMETER_PREFIX_FOR_PROPERTIES, parameter_name),\n                    PROPERTY_HINT_RANGE,\n                    vformat(\"%s,%s,0.1\", parameter_min_value, parameter_max_value)\n                  )\n                );\n            }\n\n            p_list->push_back(\n              PropertyInfo(\n                Variant::Type::INT,\n                vformat(\"%s/%s/variant_type\", FmodStringNames::EVENT_PARAMETER_PREFIX_FOR_PROPERTIES, parameter_name),\n                PROPERTY_HINT_ENUM,\n                \"\",\n                PROPERTY_USAGE_NO_EDITOR\n              )\n            );\n        }\n    }\n\n    template<class Derived, class NodeType>\n    Ref<FmodParameterDescription>\n    FmodEventEmitter<Derived, NodeType>::_get_parameter_description(const FmodEventEmitter::Parameter& parameter) const {\n        _load_event_description_if_needed();\n\n        return parameter.should_load_by_id\n               ? _event_description->get_parameter_by_id(parameter.id)\n               : _event_description->get_parameter_by_name(parameter.name);\n    }\n\n#ifdef TOOLS_ENABLED\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::tool_remove_all_parameters() {\n        if (!Engine::get_singleton()->is_editor_hint()) { return; }\n        _parameters.clear();\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::tool_remove_parameter(uint64_t parameter_id) {\n        if (!Engine::get_singleton()->is_editor_hint()) { return; }\n\n        for (int i = _parameters.size() - 1; i >= 0; --i) {\n            const Parameter& parameter {_parameters[i]};\n            if (parameter.id != parameter_id) continue;\n            _parameters.erase(parameter);\n        }\n    }\n#endif\n\n    template<class Derived, class NodeType>\n    const StringName& FmodEventEmitter<Derived, NodeType>::get_class_static() {\n        return Derived::get_class_static();\n    }\n\n    template<class Derived, class NodeType>\n    void FmodEventEmitter<Derived, NodeType>::_bind_methods() {\n        ClassDB::bind_method(D_METHOD(\"play\", \"restart_if_playing\"), &Derived::play, DEFVAL(true));\n        ClassDB::bind_method(D_METHOD(\"play_one_shot\"), &Derived::play_one_shot);\n        ClassDB::bind_method(D_METHOD(\"stop\"), &Derived::stop);\n        ClassDB::bind_method(D_METHOD(\"set_parameter\", \"name\", \"value\"), &Derived::set_parameter);\n        ClassDB::bind_method(D_METHOD(\"get_parameter\", \"name\"), &Derived::get_parameter);\n        ClassDB::bind_method(D_METHOD(\"set_parameter_by_id\", \"id\", \"value\"), &Derived::set_parameter_by_id);\n        ClassDB::bind_method(D_METHOD(\"get_parameter_by_id\", \"id\"), &Derived::get_parameter_by_id);\n        ClassDB::bind_method(D_METHOD(\"is_paused\"), &Derived::is_paused);\n        ClassDB::bind_method(D_METHOD(\"set_paused\", \"p_is_paused\"), &Derived::set_paused);\n        ClassDB::bind_method(D_METHOD(\"set_event_name\", \"event_name\"), &Derived::set_event_name);\n        ClassDB::bind_method(D_METHOD(\"get_event_name\"), &Derived::get_event_name);\n        ClassDB::bind_method(D_METHOD(\"set_event_guid\", \"event_guid\"), &Derived::set_event_guid);\n        ClassDB::bind_method(D_METHOD(\"get_event_guid\"), &Derived::get_event_guid);\n        ClassDB::bind_method(D_METHOD(\"set_attached\", \"attached\"), &Derived::set_attached);\n        ClassDB::bind_method(D_METHOD(\"is_attached\"), &Derived::is_attached);\n        ClassDB::bind_method(D_METHOD(\"set_autoplay\", \"_autoplay\"), &Derived::set_autoplay);\n        ClassDB::bind_method(D_METHOD(\"is_autoplay\"), &Derived::is_autoplay);\n        ClassDB::bind_method(D_METHOD(\"set_auto_release\", \"_autoplay\"), &Derived::set_auto_release);\n        ClassDB::bind_method(D_METHOD(\"is_auto_release\"), &Derived::is_auto_release);\n        ClassDB::bind_method(D_METHOD(\"set_allow_fadeout\", \"allow_fadeout\"), &Derived::set_allow_fadeout);\n        ClassDB::bind_method(D_METHOD(\"is_allow_fadeout\"), &Derived::is_allow_fadeout);\n        ClassDB::bind_method(D_METHOD(\"set_preload_event\", \"preload_event\"), &Derived::set_preload_event);\n        ClassDB::bind_method(D_METHOD(\"is_preload_event\"), &Derived::is_preload_event);\n        ClassDB::bind_method(D_METHOD(\"get_volume\"), &Derived::get_volume);\n        ClassDB::bind_method(D_METHOD(\"set_volume\", \"p_volume\"), &Derived::set_volume);\n        ClassDB::bind_method(D_METHOD(\"set_programmer_callback\", \"p_programmers_callback_sound_key\"), &Derived::set_programmer_callback);\n        ClassDB::bind_method(D_METHOD(\"_emit_callbacks\", \"dict\", \"type\"), &Derived::_emit_callbacks);\n\n#ifdef TOOLS_ENABLED\n        ClassDB::bind_method(D_METHOD(\"tool_remove_all_parameters\"), &Derived::tool_remove_all_parameters);\n        ClassDB::bind_method(D_METHOD(\"tool_remove_parameter\", \"parameter_id\"), &Derived::tool_remove_parameter);\n#endif\n\n        ADD_PROPERTY(PropertyInfo(Variant::STRING, \"event_name\", PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_DEFAULT), \"set_event_name\", \"get_event_name\");\n        ADD_PROPERTY(PropertyInfo(Variant::STRING, \"event_guid\", PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_DEFAULT), \"set_event_guid\", \"get_event_guid\");\n        ADD_PROPERTY(PropertyInfo(Variant::BOOL, \"attached\", PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_DEFAULT), \"set_attached\", \"is_attached\");\n        ADD_PROPERTY(PropertyInfo(Variant::BOOL, \"autoplay\", PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_DEFAULT), \"set_autoplay\", \"is_autoplay\");\n        ADD_PROPERTY(PropertyInfo(Variant::BOOL, \"auto_release\", PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_DEFAULT), \"set_auto_release\", \"is_auto_release\");\n        ADD_PROPERTY(PropertyInfo(Variant::BOOL, \"allow_fadeout\", PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_DEFAULT), \"set_allow_fadeout\", \"is_allow_fadeout\");\n        ADD_PROPERTY(PropertyInfo(Variant::BOOL, \"preload_event\", PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_DEFAULT), \"set_preload_event\", \"is_preload_event\");\n        ADD_PROPERTY(PropertyInfo(Variant::FLOAT, \"volume\", PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_DEFAULT), \"set_volume\", \"get_volume\");\n        ADD_PROPERTY(PropertyInfo(Variant::FLOAT, \"paused\", PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_NONE), \"set_paused\", \"is_paused\");\n\n        ADD_SIGNAL(MethodInfo(BEAT_SIGNAL_STRING, PropertyInfo(Variant::DICTIONARY, \"params\")));\n        ADD_SIGNAL(MethodInfo(MARKER_SIGNAL_STRING, PropertyInfo(Variant::DICTIONARY, \"params\")));\n        ADD_SIGNAL(MethodInfo(START_FAILED_SIGNAL_STRING));\n        ADD_SIGNAL(MethodInfo(STARTED_SIGNAL_STRING));\n        ADD_SIGNAL(MethodInfo(RESTARTED_SIGNAL_STRING));\n        ADD_SIGNAL(MethodInfo(STOPPED_SIGNAL_STRING));\n    }\n}// namespace godot\n#endif// GODOTFMOD_FMOD_EVENT_EMITTER_H\n"
  },
  {
    "path": "src/nodes/fmod_event_emitter_2d.cpp",
    "content": "#include <nodes/fmod_event_emitter_2d.h>\n\nusing namespace godot;\n\nvoid FmodEventEmitter2D::set_space_attribute_impl(const Ref<FmodEvent>& p_event) const {\n    p_event->set_2d_attributes(get_global_transform());\n}\n\nvoid FmodEventEmitter2D::_ready() {\n    FmodEventEmitter<FmodEventEmitter2D, Node2D>::_ready();\n}\n\nvoid FmodEventEmitter2D::_process(double delta) {\n    FmodEventEmitter<FmodEventEmitter2D, Node2D>::_process(delta);\n}\n\nvoid FmodEventEmitter2D::_notification(int p_what) {\n    FmodEventEmitter<FmodEventEmitter2D, Node2D>::_notification(p_what);\n}\n\nvoid FmodEventEmitter2D::_exit_tree() {\n    FmodEventEmitter<FmodEventEmitter2D, Node2D>::_exit_tree();\n}\n\nvoid FmodEventEmitter2D::_bind_methods() {\n    FmodEventEmitter<FmodEventEmitter2D, Node2D>::_bind_methods();\n}\n\nvoid FmodEventEmitter2D::free_impl() {\n    queue_free();\n}"
  },
  {
    "path": "src/nodes/fmod_event_emitter_2d.h",
    "content": "#ifndef FMOD_EVENT_EMITTER_2D_GODOT_FMOD_H\n#define FMOD_EVENT_EMITTER_2D_GODOT_FMOD_H\n\n#include \"fmod_event_emitter.h\"\n#include \"studio/fmod_event.h\"\n\n#include <classes/node2d.hpp>\n\nnamespace godot {\n    class FmodEventEmitter2D : public FmodEventEmitter<FmodEventEmitter2D, Node2D>  {\n        friend class FmodEventEmitter<FmodEventEmitter2D, Node2D>;\n        GDCLASS(FmodEventEmitter2D, Node2D)\n\n    private:\n        void set_space_attribute_impl(const Ref<FmodEvent>& p_event) const;\n        void free_impl();\n\n    public:\n        FmodEventEmitter2D() = default;\n        ~FmodEventEmitter2D() override = default;\n\n        virtual void _ready() override;\n        virtual  void _process(double delta) override;\n        void _notification(int p_what);\n        virtual void _exit_tree() override;\n\n    protected:\n        static void _bind_methods();\n    };\n}// namespace godot\n\n#endif// FMOD_EVENT_EMITTER_2D_GODOT_FMOD_H\n"
  },
  {
    "path": "src/nodes/fmod_event_emitter_3d.cpp",
    "content": "#include <nodes/fmod_event_emitter_3d.h>\n\nusing namespace godot;\n\nvoid FmodEventEmitter3D::set_space_attribute_impl(const Ref<FmodEvent>& p_event) const {\n    p_event->set_3d_attributes(get_global_transform());\n}\n\nvoid FmodEventEmitter3D::_ready() {\n    FmodEventEmitter<FmodEventEmitter3D, Node3D>::_ready();\n}\n\nvoid FmodEventEmitter3D::_process(double delta) {\n    FmodEventEmitter<FmodEventEmitter3D, Node3D>::_process(delta);\n}\n\nvoid FmodEventEmitter3D::_notification(int p_what) {\n    FmodEventEmitter<FmodEventEmitter3D, Node3D>::_notification(p_what);\n}\n\nvoid FmodEventEmitter3D::_exit_tree() {\n    FmodEventEmitter<FmodEventEmitter3D, Node3D>::_exit_tree();\n}\n\nvoid FmodEventEmitter3D::_bind_methods() {\n    FmodEventEmitter<FmodEventEmitter3D, Node3D>::_bind_methods();\n}\n\nvoid FmodEventEmitter3D::free_impl() {\n    queue_free();\n}\n"
  },
  {
    "path": "src/nodes/fmod_event_emitter_3d.h",
    "content": "#ifndef GODOTFMOD_FMOD_EVENT_EMITTER_3D_H\n#define GODOTFMOD_FMOD_EVENT_EMITTER_3D_H\n\n#include \"classes/node.hpp\"\n#include \"classes/node3d.hpp\"\n#include \"fmod_event_emitter.h\"\n\nnamespace godot {\n    class FmodEventEmitter3D : public FmodEventEmitter<FmodEventEmitter3D, Node3D> {\n        friend class FmodEventEmitter<FmodEventEmitter3D, Node3D>;\n        GDCLASS(FmodEventEmitter3D, Node3D)\n\n    private:\n        void set_space_attribute_impl(const Ref<FmodEvent>& p_event) const;\n        void free_impl();\n\n    public:\n        FmodEventEmitter3D() = default;\n        ~FmodEventEmitter3D() override = default;\n\n        virtual void _ready() override;\n        virtual  void _process(double delta) override;\n        void _notification(int p_what);\n        virtual void _exit_tree() override;\n\n        static void _bind_methods();\n    };\n}// namespace godot\n\n#endif// GODOTFMOD_FMOD_EVENT_EMITTER_3D_H"
  },
  {
    "path": "src/nodes/fmod_listener.h",
    "content": "#ifndef GODOTFMOD_FMOD_LISTENER_H\n#define GODOTFMOD_FMOD_LISTENER_H\n\n#include <fmod_server.h>\n\n#include <classes/engine.hpp>\n\nnamespace godot {\n    template<class Derived, class NodeType>\n    class FmodListener : public NodeType {\n\n        void ready();\n        void exit_tree();\n\n    public:\n        void _notification(int p_what);\n\n        void set_listener_index(const int index);\n        int get_listener_index() const;\n\n        void set_locked(const bool locked);\n        bool get_locked() const;\n\n        void set_listener_weight(const float p_weight);\n        float get_listener_weight() const;\n\n        static const StringName& get_class_static();\n\n        FmodListener();\n        ~FmodListener() = default;\n\n    private:\n        float _weight;\n        int _listener_index;\n        bool _is_locked;\n\n        bool _is_added;\n\n    protected:\n        static void _bind_methods();\n    };\n\n    template<class Derived, class NodeType>\n    void FmodListener<Derived, NodeType>::_notification(int p_what) {\n#ifdef TOOLS_ENABLED\n        // ensure we only run FMOD when the game is running!\n        if (Engine::get_singleton()->is_editor_hint()) { return; }\n#endif\n        switch(p_what){\n            case Node::NOTIFICATION_READY:\n                ready();\n                break;\n            case Node::NOTIFICATION_EXIT_TREE:\n                exit_tree();\n                break;\n            default:\n                break;\n        }\n    }\n\n    template<class Derived, class NodeType>\n    void FmodListener<Derived, NodeType>::ready() {\n#ifdef TOOLS_ENABLED\n        // ensure we only run FMOD when the game is running!\n        if (Engine::get_singleton()->is_editor_hint()) { return; }\n#endif\n\n        FmodServer::get_singleton()->add_listener(_listener_index, this);\n        FmodServer::get_singleton()->set_listener_lock(_listener_index, _is_locked);\n        FmodServer::get_singleton()->set_system_listener_weight(_listener_index, _weight);\n\n        _is_added = true;\n    }\n\n    template<class Derived, class NodeType>\n    void FmodListener<Derived, NodeType>::exit_tree() {\n#ifdef TOOLS_ENABLED\n        // ensure we only run FMOD when the game is running!\n        if (Engine::get_singleton()->is_editor_hint()) { return; }\n#endif\n\n        FmodServer::get_singleton()->remove_listener(_listener_index, this);\n        _is_added = false;\n    }\n\n    template<class Derived, class NodeType>\n    void FmodListener<Derived, NodeType>::set_listener_index(const int index) {\n        _listener_index = index;\n    }\n\n    template<class Derived, class NodeType>\n    int FmodListener<Derived, NodeType>::get_listener_index() const {\n        return _listener_index;\n    }\n\n    template<class Derived, class NodeType>\n    void FmodListener<Derived, NodeType>::set_locked(const bool locked) {\n        _is_locked = locked;\n\n        if (!_is_added) {\n            return;\n        }\n\n        FmodServer::get_singleton()->set_listener_lock(_listener_index, locked);\n    }\n\n    template<class Derived, class NodeType>\n    bool FmodListener<Derived, NodeType>::get_locked() const {\n        if (!_is_added) {\n            return _is_locked;\n        }\n\n        return FmodServer::get_singleton()->get_listener_lock(_listener_index);\n    }\n\n    template<class Derived, class NodeType>\n    void FmodListener<Derived, NodeType>::set_listener_weight(const float p_weight) {\n        _weight = p_weight;\n\n        if (!_is_added) {\n            return;\n        }\n\n        FmodServer::get_singleton()->set_system_listener_weight(_listener_index, p_weight);\n    }\n\n    template<class Derived, class NodeType>\n    float FmodListener<Derived, NodeType>::get_listener_weight() const {\n        if (!_is_added) {\n            return _weight;\n        }\n\n        return FmodServer::get_singleton()->get_system_listener_weight(_listener_index);\n    }\n\n    template<class Derived, class NodeType>\n    FmodListener<Derived, NodeType>::FmodListener() :\n      NodeType(),\n      _weight(1.0),\n      _listener_index(0),\n      _is_locked(false),\n      _is_added(false) {}\n\n    template<class Derived, class NodeType>\n    void FmodListener<Derived, NodeType>::_bind_methods() {\n        ClassDB::bind_method(D_METHOD(\"set_listener_index\", \"index\"), &Derived::set_listener_index);\n        ClassDB::bind_method(D_METHOD(\"get_listener_index\"), &Derived::get_listener_index);\n        ClassDB::bind_method(D_METHOD(\"set_locked\", \"locked\"), &Derived::set_locked);\n        ClassDB::bind_method(D_METHOD(\"get_locked\"), &Derived::get_locked);\n        ClassDB::bind_method(D_METHOD(\"set_listener_weight\", \"p_weight\"), &Derived::set_listener_weight);\n        ClassDB::bind_method(D_METHOD(\"get_listener_weight\"), &Derived::get_listener_weight);\n\n        ADD_PROPERTY(\n          PropertyInfo(\n            Variant::INT,\n            \"listener_index\",\n            PROPERTY_HINT_NONE,\n            \"\",\n            PROPERTY_USAGE_DEFAULT\n          ),\n          \"set_listener_index\",\n          \"get_listener_index\"\n        );\n        ADD_PROPERTY(\n          PropertyInfo(\n            Variant::BOOL,\n            \"is_locked\",\n            PROPERTY_HINT_NONE,\n            \"\",\n            PROPERTY_USAGE_DEFAULT\n          ),\n          \"set_locked\",\n          \"get_locked\"\n        );\n        ADD_PROPERTY(\n          PropertyInfo(\n            Variant::FLOAT,\n            \"weight\",\n            PROPERTY_HINT_NONE,\n            \"\",\n            PROPERTY_USAGE_DEFAULT\n          ),\n          \"set_listener_weight\",\n          \"get_listener_weight\"\n        );\n    }\n\n    template<class Derived, class NodeType>\n    const StringName& FmodListener<Derived, NodeType>::get_class_static() {\n        return Derived::get_class_static();\n    }\n}// namespace godot\n\n#endif// GODOTFMOD_FMOD_LISTENER_H\n"
  },
  {
    "path": "src/nodes/fmod_listener_2d.cpp",
    "content": "#include \"fmod_listener_2d.h\"\n\nusing namespace godot;\n\nvoid FmodListener2D::_bind_methods() {\n    FmodListener<FmodListener2D, Node2D>::_bind_methods();\n}\n\nvoid FmodListener2D::_ready() {\n    FmodListener<FmodListener2D, Node2D>::_ready();\n}\n\nvoid FmodListener2D::_exit_tree() {\n    FmodListener<FmodListener2D, Node2D>::_exit_tree();\n}\n"
  },
  {
    "path": "src/nodes/fmod_listener_2d.h",
    "content": "#ifndef GODOTFMOD_FMOD_LISTENER_2D_H\n#define GODOTFMOD_FMOD_LISTENER_2D_H\n\n#include \"classes/node.hpp\"\n#include \"classes/node2d.hpp\"\n#include \"fmod_listener.h\"\n\nnamespace godot {\n    class FmodListener2D : public FmodListener<FmodListener2D, Node2D> {\n        GDCLASS(FmodListener2D, Node2D)\n\n    public:\n        virtual void _ready() override;\n        virtual void _exit_tree() override;\n\n    protected:\n\n        static void _bind_methods();\n    };\n}// namespace godot\n\n#endif// GODOTFMOD_FMOD_LISTENER_2D_H\n"
  },
  {
    "path": "src/nodes/fmod_listener_3d.cpp",
    "content": "#include \"fmod_listener_3d.h\"\n\n#include \"fmod_server.h\"\n\nusing namespace godot;\n\nvoid FmodListener3D::_bind_methods() {\n    FmodListener<FmodListener3D, Node3D>::_bind_methods();\n}\n\nvoid FmodListener3D::_ready() {\n    FmodListener<FmodListener3D, Node3D>::_ready();\n}\n\nvoid FmodListener3D::_exit_tree() {\n    FmodListener<FmodListener3D, Node3D>::_exit_tree();\n}\n"
  },
  {
    "path": "src/nodes/fmod_listener_3d.h",
    "content": "#ifndef GODOTFMOD_FMOD_LISTENER_3D_H\n#define GODOTFMOD_FMOD_LISTENER_3D_H\n\n#include \"classes/node.hpp\"\n#include \"classes/node3d.hpp\"\n#include \"fmod_listener.h\"\n\nnamespace godot {\n    class FmodListener3D : public FmodListener<FmodListener3D, Node3D> {\n        GDCLASS(FmodListener3D, Node3D)\n\n    public:\n        virtual void _ready() override;\n        virtual void _exit_tree() override;\n\n    protected:\n\n        static void _bind_methods();\n    };\n}// namespace godot\n\n#endif// GODOTFMOD_FMOD_LISTENER_3D_H\n"
  },
  {
    "path": "src/plugins/ios_plugins_loader.h",
    "content": "#ifndef GODOTFMOD_IOS_PLUGINS_LOADER_H\n#define GODOTFMOD_IOS_PLUGINS_LOADER_H\n\n#ifdef IOS_ENABLED\ntypedef void* FMOD_SYSTEM_PTR;\n\ntypedef uint32_t (*REGISTER_DSP_METHOD)(FMOD_SYSTEM_PTR system, FMOD_DSP_DESCRIPTION* description, uint32_t* handle);\ntypedef uint32_t (*REGISTER_CODEC_METHOD)(FMOD_SYSTEM_PTR system, FMOD_CODEC_DESCRIPTION* description, uint32_t* handle);\ntypedef uint32_t (*REGISTER_OUTPUT_METHOD)(FMOD_SYSTEM_PTR system, FMOD_OUTPUT_DESCRIPTION* description, uint32_t* handle);\n\ntypedef struct {\n    FMOD_SYSTEM_PTR system;\n    REGISTER_DSP_METHOD register_dsp_method;\n    REGISTER_CODEC_METHOD register_codec_method;\n    REGISTER_OUTPUT_METHOD register_output_method;\n} FMOD_IOS_INTERFACE;\n\nextern \"C\" {\n    uint32_t* load_all_fmod_plugins(FMOD_IOS_INTERFACE* ios_interface, uint32_t* r_count);\n};\n#endif\n\n#endif //GODOTFMOD_IOS_PLUGINS_LOADER_H\n"
  },
  {
    "path": "src/plugins/plugins_helper.h",
    "content": "#ifndef GODOTFMOD_PLUGINS_HELPER_H\n#define GODOTFMOD_PLUGINS_HELPER_H\n\n#include <variant/string.hpp>\n#include <resources/fmod_plugins_settings.h>\n#include <classes/os.hpp>\n\nnamespace godot {\n    static String get_fmod_plugins_base_path(const Ref<FmodPluginsSettings>& p_settings) {\n#ifdef TOOLS_ENABLED\n        return p_settings->get_plugins_base_path();\n#else\n        return OS::get_singleton()\n            ->get_executable_path()\n            .get_base_dir()\n    #ifdef MACOS_ENABLED\n            .path_join(\"../PlugIns/\")\n#endif\n            ;\n#endif\n    }\n\n#if !defined(ANDROID_ENABLED) || defined(TOOLS_ENABLED)\n    static String get_plugins_os_directory(const Ref<FmodPluginsSettings>& p_settings, const String& p_os_lower_name, const String& p_arch) {\n        String plugin_directory = get_fmod_plugins_base_path(p_settings);\n\n#ifdef TOOLS_ENABLED\n        plugin_directory = plugin_directory.path_join(p_os_lower_name);\n\n        if (!p_arch.is_empty()) {\n            plugin_directory = plugin_directory.path_join(p_arch);\n        }\n#endif\n        return plugin_directory;\n    }\n#endif\n\n    static Vector<String> get_fmod_plugins_libraries_paths(const Ref<FmodPluginsSettings>& p_settings, const String& os_override = \"\", const String& arch = \"\") {\n        Vector<String> result;\n\n#if !defined(ANDROID_ENABLED) || defined(TOOLS_ENABLED)\n        String os_lower_name = os_override.is_empty() ? OS::get_singleton()->get_name().to_lower() : os_override.to_lower();\n        String plugin_directory = get_plugins_os_directory(p_settings, os_lower_name, arch);\n\n        String plugin_extension;\n        String plugin_lib_prefix = \"lib\";\n        if (os_lower_name == \"windows\") {\n            plugin_extension = \"dll\";\n            plugin_lib_prefix = \"\";\n        } else if (os_lower_name == \"macos\") {\n            plugin_extension = \"dylib\";\n        } else {\n            plugin_extension = \"so\";\n        }\n\n        PackedStringArray plugin_list = p_settings->get_dynamic_plugin_list();\n\n        for (const String& plugin : plugin_list) {\n            result.append(plugin_directory.path_join(vformat(\"%s%s.%s\", plugin_lib_prefix, plugin, plugin_extension)));\n        }\n#else\n        for (const String& plugin : p_settings->get_dynamic_plugin_list()) {\n            result.append(vformat(\"lib%s.so\", plugin));\n        }\n#endif\n\n        return result;\n    }\n}\n\n#endif //GODOTFMOD_PLUGINS_HELPER_H\n"
  },
  {
    "path": "src/register_types.cpp",
    "content": "#include \"constants.h\"\n#include \"core/fmod_sound.h\"\n#include \"data/performance_data.h\"\n#include \"fmod_server.h\"\n#include \"nodes/fmod_bank_loader.h\"\n#include \"nodes/fmod_event_emitter_2d.h\"\n#include \"nodes/fmod_event_emitter_3d.h\"\n#include \"nodes/fmod_listener_2d.h\"\n#include \"nodes/fmod_listener_3d.h\"\n#include \"studio/fmod_bank.h\"\n#include \"studio/fmod_bus.h\"\n#include \"studio/fmod_event.h\"\n#include \"studio/fmod_event_description.h\"\n#include \"studio/fmod_vca.h\"\n#include \"fmod_string_names.h\"\n#include \"resources/fmod_logging_settings.h\"\n\n#ifdef TOOLS_ENABLED\n#include <tools//fmod_editor_export_plugin.h>\n#endif\n\n#include <register_types.h>\n#include <resources/fmod_dsp_settings.h>\n#include <resources/fmod_software_format_settings.h>\n#include <studio/fmod_parameter_description.h>\n#include <tools/fmod_editor_plugin.h>\n\n#include <classes/engine.hpp>\n#include <classes/project_settings.hpp>\n\nusing namespace godot;\n\nstatic FmodServer* fmod_singleton;\n\nvoid initialize_fmod_with_settings() {\n    Ref<FmodGeneralSettings> general_settings = FmodGeneralSettings::get_from_project_settings();\n    Ref<FmodSoftwareFormatSettings> software_format_settings = FmodSoftwareFormatSettings::get_from_project_settings();\n    Ref<FmodDspSettings> dsp_settings = FmodDspSettings::get_from_project_settings();\n    Ref<FmodSound3DSettings> three_d_settings = FmodSound3DSettings::get_from_project_settings();\n\n    FmodServer::get_singleton()->set_software_format(software_format_settings);\n    FmodServer::get_singleton()->set_system_dsp_buffer_size(dsp_settings);\n    FmodServer::get_singleton()->init(general_settings);\n    FmodServer::get_singleton()->set_sound_3d_settings(three_d_settings);\n    FmodServer::get_singleton()->set_system_listener_number(general_settings->get_default_listener_count());\n\n    FmodServer::get_singleton()->load_all_plugins(FmodPluginsSettings::get_from_project_settings());\n}\n\nvoid initialize_fmod() {\n#ifdef TOOLS_ENABLED\n    if (Engine::get_singleton()->is_editor_hint()) {\n        initialize_fmod_with_settings();\n        return;\n    }\n#endif\n\n    bool auto_initialize = ProjectSettings::get_singleton()->get_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodGeneralSettings::INITIALIZE_BASE_PATH, FMOD_SETTING_AUTO_INITIALIZE),\n      DEFAULT_AUTO_INITIALIZE\n    );\n\n    if (auto_initialize) {\n        initialize_fmod_with_settings();\n    }\n}\n\nvoid initialize_fmod_module(ModuleInitializationLevel p_level) {\n    if (p_level == MODULE_INITIALIZATION_LEVEL_CORE) {\n        // initialise filerunner singleton by calling it.\n        FmodStringNames::create();\n        Callbacks::GodotFileRunner::get_singleton();\n    }\n    if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {\n        // Data\n        ClassDB::register_class<FmodPerformanceData>();\n\n        // Core\n        ClassDB::register_class<FmodSound>();\n        ClassDB::register_class<FmodFile>();\n\n        // Studio\n        ClassDB::register_class<FmodBank>();\n        ClassDB::register_class<FmodEventDescription>();\n        ClassDB::register_class<FmodParameterDescription>();\n\n        ClassDB::register_class<FmodEvent>();\n        ClassDB::register_class<FmodBus>();\n        ClassDB::register_class<FmodVCA>();\n\n        // Nodes\n        ClassDB::register_class<FmodListener3D>();\n        ClassDB::register_class<FmodListener2D>();\n        ClassDB::register_class<FmodEventEmitter2D>();\n        ClassDB::register_class<FmodEventEmitter3D>();\n        ClassDB::register_class<FmodBankLoader>();\n\n        // Resources\n        ClassDB::register_class<FmodGeneralSettings>();\n        ClassDB::register_class<FmodSoftwareFormatSettings>();\n        ClassDB::register_class<FmodDspSettings>();\n        ClassDB::register_class<FmodSound3DSettings>();\n        ClassDB::register_class<FmodStaticPluginMethod>();\n        ClassDB::register_class<FmodPluginsSettings>();\n        ClassDB::register_class<FmodLoggingSettings>();\n\n        // Server\n        ClassDB::register_class<FmodServer>();\n        fmod_singleton = memnew(FmodServer);\n        Engine::get_singleton()->register_singleton(\"FmodServer\", FmodServer::get_singleton());\n        initialize_fmod();\n    }\n#ifdef TOOLS_ENABLED\n    if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {\n        ClassDB::register_class<FmodEditorExportPlugin>();\n        ClassDB::register_class<FmodEditorPlugin>();\n        EditorPlugins::add_by_type<FmodEditorPlugin>();\n    }\n#endif\n}\n\nvoid uninitialize_fmod_module(ModuleInitializationLevel p_level) {\n    if (p_level == MODULE_INITIALIZATION_LEVEL_CORE) {\n        Callbacks::GodotFileRunner::get_singleton()->finish();\n        FmodStringNames::free();\n    }\n    if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {\n        fmod_singleton->shutdown();\n\n        Engine::get_singleton()->unregister_singleton(\"FmodServer\");\n        memdelete(fmod_singleton);\n    }\n}\n\nextern \"C\" {\n\n// Initialization.\n\nGDExtensionBool GDE_EXPORT\nfmod_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) {\n    GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);\n\n    init_obj.register_initializer(initialize_fmod_module);\n    init_obj.register_terminator(uninitialize_fmod_module);\n    init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_CORE);\n\n    return init_obj.init();\n}\n}\n"
  },
  {
    "path": "src/register_types.h",
    "content": "#ifndef FMOD_REGISTER_TYPES_H\n#define FMOD_REGISTER_TYPES_H\n\n#include <core/class_db.hpp>\n\nusing namespace godot;\n\nvoid initialize_fmod_module(ModuleInitializationLevel p_level);\nvoid uninitialize_fmod_module(ModuleInitializationLevel p_level);\n\n#endif// ! FMOD_REGISTER_TYPES_H"
  },
  {
    "path": "src/resources/fmod_dsp_settings.cpp",
    "content": "#include \"fmod_dsp_settings.h\"\n\n#include <constants.h>\n\n#include <classes/project_settings.hpp>\n\nusing namespace godot;\n\nvoid FmodDspSettings::set_dsp_buffer_size(const unsigned int p_dsp_buffer_size) {\n    _dsp_buffer_size = p_dsp_buffer_size;\n}\n\nunsigned int FmodDspSettings::get_dsp_buffer_size() const {\n    return _dsp_buffer_size;\n}\n\nvoid FmodDspSettings::set_dsp_buffer_count(const int p_dsp_buffer_count) {\n    _dsp_buffer_count = p_dsp_buffer_count;\n}\n\nint FmodDspSettings::get_dsp_buffer_count() const {\n    return _dsp_buffer_count;\n}\n\nRef<FmodDspSettings> FmodDspSettings::get_from_project_settings() {\n    Ref<FmodDspSettings> settings;\n    settings.instantiate();\n\n    ProjectSettings* project_settings = ProjectSettings::get_singleton();\n\n    String dsp_buffer_size_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, DSP_SETTINGS_BASE_PATH, DSP_BUFFER_SIZE_OPTION);\n    settings->set_dsp_buffer_size(project_settings->get_setting(dsp_buffer_size_setting_path, DEFAULT_DSP_BUFFER_SIZE));\n\n    String dsp_buffer_count_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, DSP_SETTINGS_BASE_PATH, DSP_BUFFER_COUNT_OPTION);\n    settings->set_dsp_buffer_count(project_settings->get_setting(dsp_buffer_count_setting_path, DEFAULT_DSP_BUFFER_COUNT));\n\n    return settings;\n}\n\nvoid FmodDspSettings::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"set_dsp_buffer_size\", \"p_dsp_buffer_size\"), &FmodDspSettings::set_dsp_buffer_size);\n    ClassDB::bind_method(D_METHOD(\"get_dsp_buffer_size\"), &FmodDspSettings::get_dsp_buffer_size);\n\n    ClassDB::bind_method(D_METHOD(\"set_dsp_buffer_count\", \"p_dsp_buffer_count\"), &FmodDspSettings::set_dsp_buffer_count);\n    ClassDB::bind_method(D_METHOD(\"get_dsp_buffer_count\"), &FmodDspSettings::get_dsp_buffer_count);\n\n    ADD_PROPERTY(\n      PropertyInfo(\n        Variant::INT,\n        \"dsp_buffer_size\",\n        PROPERTY_HINT_NONE,\n        \"\",\n        PROPERTY_USAGE_DEFAULT\n      ),\n      \"set_dsp_buffer_size\",\n      \"get_dsp_buffer_size\"\n    );\n\n    ADD_PROPERTY(\n      PropertyInfo(\n        Variant::INT,\n        \"dsp_buffer_count\",\n        PROPERTY_HINT_NONE,\n        \"\",\n        PROPERTY_USAGE_DEFAULT\n      ),\n      \"set_dsp_buffer_count\",\n      \"get_dsp_buffer_count\"\n    );\n}\n"
  },
  {
    "path": "src/resources/fmod_dsp_settings.h",
    "content": "#ifndef GODOTFMOD_FMOD_DSP_SETTINGS_H\n#define GODOTFMOD_FMOD_DSP_SETTINGS_H\n\n#include <classes/resource.hpp>\n\nnamespace godot {\n    class FmodDspSettings : public Resource {\n        GDCLASS(FmodDspSettings, Resource)\n\n    public:\n        void set_dsp_buffer_size(const unsigned int p_dsp_buffer_size);\n        unsigned int get_dsp_buffer_size() const;\n\n        void set_dsp_buffer_count(const int p_dsp_buffer_count);\n        int get_dsp_buffer_count() const;\n\n        static Ref<FmodDspSettings> get_from_project_settings();\n\n        FmodDspSettings() = default;\n        ~FmodDspSettings() = default;\n\n        static constexpr const char* DSP_SETTINGS_BASE_PATH = \"DSP\";\n\n        static constexpr const char* DSP_BUFFER_SIZE_OPTION = \"dsp_buffer_size\";\n        static constexpr const char* DSP_BUFFER_COUNT_OPTION = \"dsp_buffer_count\";\n\n        static constexpr const int DEFAULT_DSP_BUFFER_SIZE = 512;\n        static constexpr const int DEFAULT_DSP_BUFFER_COUNT = 4;\n\n    private:\n        unsigned int _dsp_buffer_size;\n        int _dsp_buffer_count;\n\n    protected:\n        static void _bind_methods();\n    };\n}\n\n#endif// GODOTFMOD_FMOD_DSP_SETTINGS_H\n"
  },
  {
    "path": "src/resources/fmod_logging_settings.cpp",
    "content": "#include \"fmod_logging_settings.h\"\n\n#include <constants.h>\n\n#include <classes/project_settings.hpp>\n#include <godot_cpp/classes/os.hpp>\n\nusing namespace godot;\n\nvoid FmodLoggingSettings::set_debug_level(int p_debug_level) {\n    _debug_level = p_debug_level;\n}\n\nint FmodLoggingSettings::get_debug_level() const {\n    return _debug_level;\n}\n\nint FmodLoggingSettings::_debug_level_to_fmod() const {\n       switch (_debug_level) {\n        case DEBUG_INHERIT:\n            if (OS::get_singleton()->is_stdout_verbose()) {\n                return FMOD_DEBUG_LEVEL_ERROR | FMOD_DEBUG_LEVEL_WARNING | FMOD_DEBUG_LEVEL_LOG | FMOD_DEBUG_TYPE_TRACE;\n            } else {\n                return FMOD_DEBUG_LEVEL_ERROR | FMOD_DEBUG_LEVEL_WARNING;\n            }\n        case DEBUG_NONE:\n            return FMOD_DEBUG_LEVEL_NONE;\n        case DEBUG_ERROR:\n            return FMOD_DEBUG_LEVEL_ERROR;\n        case DEBUG_WARNING:\n            return FMOD_DEBUG_LEVEL_ERROR | FMOD_DEBUG_LEVEL_WARNING;\n        case DEBUG_LOG:\n            return FMOD_DEBUG_LEVEL_ERROR | FMOD_DEBUG_LEVEL_WARNING | FMOD_DEBUG_LEVEL_LOG;\n        case DEBUG_VERBOSE:\n            return FMOD_DEBUG_LEVEL_ERROR | FMOD_DEBUG_LEVEL_WARNING | FMOD_DEBUG_LEVEL_LOG | FMOD_DEBUG_TYPE_TRACE;\n        default:\n            return FMOD_DEBUG_LEVEL_ERROR | FMOD_DEBUG_LEVEL_WARNING;\n    }\n}\n\nvoid FmodLoggingSettings::set_log_output(int p_log_output) {\n    _log_output = p_log_output;\n}\n\nint FmodLoggingSettings::get_log_output() const {\n    return _log_output;\n}\n\nvoid FmodLoggingSettings::set_log_file_path(const String& p_log_file_path) {\n    _log_file_path = p_log_file_path;\n}\n\nconst String& FmodLoggingSettings::get_log_file_path() const {\n    return _log_file_path;\n}\n\nRef<FmodLoggingSettings> FmodLoggingSettings::get_from_project_settings() {\n    Ref<FmodLoggingSettings> settings;\n    settings.instantiate();\n\n    ProjectSettings* project_settings = ProjectSettings::get_singleton();\n\n    String debug_level_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, LOGGING_SETTINGS_BASE_PATH, DEBUG_LEVEL_OPTION);\n    settings->set_debug_level(project_settings->get_setting(debug_level_setting_path, DEFAULT_DEBUG_LEVEL));\n\n    String log_output_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, LOGGING_SETTINGS_BASE_PATH, LOG_OUTPUT_OPTION);\n    settings->set_log_output(project_settings->get_setting(log_output_setting_path, DEFAULT_LOG_OUTPUT));\n\n    String log_file_path_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, LOGGING_SETTINGS_BASE_PATH, LOG_FILE_PATH_OPTION);\n    settings->set_log_file_path(project_settings->get_setting(log_file_path_setting_path, DEFAULT_LOG_FILE_PATH));\n\n    return settings;\n}\n\nvoid FmodLoggingSettings::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"set_debug_level\", \"debug_level\"), &FmodLoggingSettings::set_debug_level);\n    ClassDB::bind_method(D_METHOD(\"get_debug_level\"), &FmodLoggingSettings::get_debug_level);\n\n    ClassDB::bind_method(D_METHOD(\"set_log_output\", \"log_output\"), &FmodLoggingSettings::set_log_output);\n    ClassDB::bind_method(D_METHOD(\"get_log_output\"), &FmodLoggingSettings::get_log_output);\n\n    ClassDB::bind_method(D_METHOD(\"set_log_file_path\", \"log_file_path\"), &FmodLoggingSettings::set_log_file_path);\n    ClassDB::bind_method(D_METHOD(\"get_log_file_path\"), &FmodLoggingSettings::get_log_file_path);\n\n    // Debug level property\n    ADD_PROPERTY(\n      PropertyInfo(Variant::INT, \"debug_level\", PROPERTY_HINT_ENUM, \"None,Error Only,Error and Warning,Full Log,Verbose\"),\n      \"set_debug_level\",\n      \"get_debug_level\"\n    );\n\n    // Log output property\n    ADD_PROPERTY(PropertyInfo(Variant::INT, \"log_output\", PROPERTY_HINT_ENUM, \"TTY,Godot,File\"), \"set_log_output\", \"get_log_output\");\n\n    // Log file path property\n    ADD_PROPERTY(PropertyInfo(Variant::STRING, \"log_file_path\", PROPERTY_HINT_FILE, \"*.txt,*.log\", PROPERTY_USAGE_DEFAULT), \"set_log_file_path\", \"get_log_file_path\");\n\n    // Bind enum constants\n    BIND_ENUM_CONSTANT(DEBUG_NONE);\n    BIND_ENUM_CONSTANT(DEBUG_ERROR);\n    BIND_ENUM_CONSTANT(DEBUG_WARNING);\n    BIND_ENUM_CONSTANT(DEBUG_LOG);\n    BIND_ENUM_CONSTANT(DEBUG_VERBOSE);\n\n    BIND_ENUM_CONSTANT(FMOD_DEBUG_MODE_TTY);\n    BIND_ENUM_CONSTANT(FMOD_DEBUG_MODE_CALLBACK);\n    BIND_ENUM_CONSTANT(FMOD_DEBUG_MODE_FILE);\n}\n"
  },
  {
    "path": "src/resources/fmod_logging_settings.h",
    "content": "#ifndef GODOTFMOD_FMOD_LOGGING_SETTINGS_H\n#define GODOTFMOD_FMOD_LOGGING_SETTINGS_H\n\n#include \"fmod_studio.h\"\n\n#include <classes/resource.hpp>\n\nnamespace godot {\n\n    class FmodLoggingSettings : public Resource {\n        GDCLASS(FmodLoggingSettings, Resource)\n\n    public:\n        enum DebugLevel {\n            DEBUG_INHERIT,\n            DEBUG_NONE,\n            DEBUG_ERROR,\n            DEBUG_WARNING,\n            DEBUG_LOG,\n            DEBUG_VERBOSE,\n        };\n\n        void set_debug_level(int p_debug_level);\n        int get_debug_level() const;\n        int _debug_level_to_fmod() const;\n\n        void set_log_output(int p_log_output);\n        int get_log_output() const;\n\n        void set_log_file_path(const String& p_log_file_path);\n        const String& get_log_file_path() const;\n\n        static Ref<FmodLoggingSettings> get_from_project_settings();\n\n        static constexpr const char* LOGGING_SETTINGS_BASE_PATH = \"Logging\";\n\n        // Setting keys\n        static constexpr const char* DEBUG_LEVEL_OPTION = \"debug_level\";\n        static constexpr const char* LOG_OUTPUT_OPTION = \"log_output\";\n        static constexpr const char* LOG_FILE_PATH_OPTION = \"log_file_path\";\n\n        // Default values\n        static constexpr const int DEFAULT_DEBUG_LEVEL = DEBUG_INHERIT;\n        static constexpr const int DEFAULT_LOG_OUTPUT = FMOD_DEBUG_MODE_CALLBACK;\n        static constexpr const char* DEFAULT_LOG_FILE_PATH = \"user://fmod.log\";\n\n    private:\n        int _debug_level;\n        int _log_output;\n        String _log_file_path;\n\n    protected:\n        static void _bind_methods();\n    };\n\n}// namespace godot\n\nVARIANT_ENUM_CAST(FmodLoggingSettings::DebugLevel);\nVARIANT_ENUM_CAST(FMOD_DEBUG_MODE);\n\n#endif// GODOTFMOD_FMOD_LOGGING_SETTINGS_H\n"
  },
  {
    "path": "src/resources/fmod_plugins_settings.cpp",
    "content": "#include \"fmod_plugins_settings.h\"\n\n#include \"constants.h\"\n\n#include <helpers/common.h>\n\n#include <classes/file_access.hpp>\n#include <classes/project_settings.hpp>\n#include <classes/resource_loader.hpp>\n\nusing namespace godot;\n\nvoid FmodPluginsSettings::set_plugins_base_path(const String& p_base_path) {\n    _plugins_base_path = p_base_path;\n}\n\nconst String& FmodPluginsSettings::get_plugins_base_path() const {\n    return _plugins_base_path;\n}\n\nvoid FmodPluginsSettings::set_dynamic_plugin_list(const PackedStringArray& p_dynamic_plugin_list) {\n    _dynamic_plugin_list = p_dynamic_plugin_list;\n}\n\nconst PackedStringArray& FmodPluginsSettings::get_dynamic_plugin_list() const {\n    return _dynamic_plugin_list;\n}\n\nRef<FmodPluginsSettings> FmodPluginsSettings::get_from_project_settings() {\n    ProjectSettings* project_settings = ProjectSettings::get_singleton();\n\n    String resource_path = project_settings->get_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, PLUGINS_SETTINGS_BASE_PATH, RESOURCE_OPTION),\n      FmodPluginsSettings::DEFAULT_RESOURCE_OPTION\n    );\n    if (resource_path.is_empty()) {\n        Ref<FmodPluginsSettings> settings;\n        settings.instantiate();\n        return settings;\n    }\n\n    if (!FileAccess::file_exists(resource_path)) {\n        GODOT_LOG_WARNING(vformat(\"Cannot find FmodPluginsSettings at %s\", resource_path));\n\n        Ref<FmodPluginsSettings> settings;\n        settings.instantiate();\n        return settings;\n    }\n\n    Ref<FmodPluginsSettings> settings = ResourceLoader::get_singleton()->load(resource_path);\n    return settings;\n}\n\nvoid FmodPluginsSettings::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"set_plugins_base_path\", \"p_base_path\"), &FmodPluginsSettings::set_plugins_base_path);\n    ClassDB::bind_method(D_METHOD(\"get_fmod_plugins_base_path\"), &FmodPluginsSettings::get_plugins_base_path);\n\n    ClassDB::bind_method(D_METHOD(\"set_dynamic_plugin_list\", \"p_dynamic_plugin_list\"), &FmodPluginsSettings::set_dynamic_plugin_list);\n    ClassDB::bind_method(D_METHOD(\"get_dynamic_plugin_list\"), &FmodPluginsSettings::get_dynamic_plugin_list);\n\n    ClassDB::bind_method(D_METHOD(\"set_static_plugins_methods\", \"p_static_plugins_settings\"), &FmodPluginsSettings::set_static_plugins_methods);\n    ClassDB::bind_method(D_METHOD(\"get_static_plugins_methods\"), &FmodPluginsSettings::get_static_plugins_methods);\n\n    ADD_PROPERTY(\n            PropertyInfo(\n                    Variant::STRING,\n                    \"plugins_base_path\",\n                    PROPERTY_HINT_DIR,\n                    \"\",\n                    PROPERTY_USAGE_DEFAULT\n            ),\n            \"set_plugins_base_path\",\n            \"get_fmod_plugins_base_path\"\n    );\n\n    ADD_PROPERTY(\n            PropertyInfo(\n                    Variant::PACKED_STRING_ARRAY,\n                    \"dynamic_plugin_list\",\n                    PROPERTY_HINT_NONE,\n                    \"\",\n                    PROPERTY_USAGE_DEFAULT\n            ),\n            \"set_dynamic_plugin_list\",\n            \"get_dynamic_plugin_list\"\n    );\n\n    ADD_PROPERTY(\n            PropertyInfo(\n                    Variant::ARRAY,\n                    \"static_plugins_methods\",\n                    PROPERTY_HINT_ARRAY_TYPE,\n                    vformat(\"%s/%s:%s\", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, \"FmodStaticPluginMethod\"),\n                    PROPERTY_USAGE_DEFAULT\n            ),\n            \"set_static_plugins_methods\",\n            \"get_static_plugins_methods\"\n    );\n}\n\nvoid FmodPluginsSettings::set_static_plugins_methods(const Array& p_static_plugins_settings) {\n    _static_plugins_methods = p_static_plugins_settings;\n}\n\nconst Array& FmodPluginsSettings::get_static_plugins_methods() const {\n    return _static_plugins_methods;\n}\n\nvoid FmodStaticPluginMethod::set_type(FmodStaticPluginMethod::Type p_type) {\n    _type = p_type;\n}\n\nFmodStaticPluginMethod::Type FmodStaticPluginMethod::get_type() const {\n    return _type;\n}\n\nvoid FmodStaticPluginMethod::set_method_name(const String& p_method_name) {\n    _method_name = p_method_name;\n}\n\nconst String& FmodStaticPluginMethod::get_method_name() const {\n    return _method_name;\n}\n\nvoid FmodStaticPluginMethod::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"set_type\", \"p_type\"), &FmodStaticPluginMethod::set_type);\n    ClassDB::bind_method(D_METHOD(\"get_type\"), &FmodStaticPluginMethod::get_type);\n\n    ClassDB::bind_method(D_METHOD(\"set_method_name\", \"p_method_name\"), &FmodStaticPluginMethod::set_method_name);\n    ClassDB::bind_method(D_METHOD(\"get_method_name\"), &FmodStaticPluginMethod::get_method_name);\n\n    ADD_PROPERTY(\n            PropertyInfo(\n                    Variant::INT,\n                    \"type\",\n                    PROPERTY_HINT_ENUM,\n                    \"CODEC,DSP,OUTPUT\",\n                    PROPERTY_USAGE_DEFAULT\n            ),\n            \"set_type\",\n            \"get_type\"\n    );\n\n    ADD_PROPERTY(\n            PropertyInfo(\n                    Variant::STRING,\n                    \"method_name\",\n                    PROPERTY_HINT_NONE,\n                    \"\",\n                    PROPERTY_USAGE_DEFAULT\n            ),\n            \"set_method_name\",\n            \"get_method_name\"\n    );\n\n    BIND_ENUM_CONSTANT(CODEC);\n    BIND_ENUM_CONSTANT(DSP);\n    BIND_ENUM_CONSTANT(OUTPUT);\n}"
  },
  {
    "path": "src/resources/fmod_plugins_settings.h",
    "content": "#ifndef GODOTFMOD_FMOD_PLUGINS_SETTINGS_H\n#define GODOTFMOD_FMOD_PLUGINS_SETTINGS_H\n\n\n#include <classes/resource.hpp>\n\nnamespace godot {\n    class FmodStaticPluginMethod : public Resource {\n        GDCLASS(FmodStaticPluginMethod, Resource)\n\n    public:\n        enum Type {\n            CODEC,\n            DSP,\n            OUTPUT,\n\n            COUNT\n        };\n\n        void set_type(Type p_type);\n        Type get_type() const;\n\n        void set_method_name(const String& p_method_name);\n        const String& get_method_name() const;\n\n    private:\n        Type _type = Type::CODEC;\n        String _method_name;\n\n    protected:\n        static void _bind_methods();\n    };\n\n    class FmodPluginsSettings : public Resource {\n        GDCLASS(FmodPluginsSettings, Resource)\n\n    public:\n        void set_plugins_base_path(const String& p_base_path);\n        const String& get_plugins_base_path() const;\n\n        void set_dynamic_plugin_list(const PackedStringArray& p_dynamic_plugin_list);\n        const PackedStringArray& get_dynamic_plugin_list() const;\n\n        void set_static_plugins_methods(const Array& p_static_plugins_settings);\n        const Array& get_static_plugins_methods() const;\n\n        static Ref<FmodPluginsSettings> get_from_project_settings();\n\n        FmodPluginsSettings() = default;\n        ~FmodPluginsSettings() = default;\n\n        static constexpr const char* PLUGINS_SETTINGS_BASE_PATH = \"Plugins\";\n\n        static constexpr const char* RESOURCE_OPTION = \"path_to_plugin_configuration\";\n        static constexpr const char* DEFAULT_RESOURCE_OPTION = \"\";\n\n    private:\n        String _plugins_base_path;\n        PackedStringArray _dynamic_plugin_list;\n        Array _static_plugins_methods;\n\n    protected:\n        static void _bind_methods();\n    };\n}\n\nVARIANT_ENUM_CAST(godot::FmodStaticPluginMethod::Type);\n\n\n#endif //GODOTFMOD_FMOD_PLUGINS_SETTINGS_H\n"
  },
  {
    "path": "src/resources/fmod_settings.cpp",
    "content": "#include \"fmod_settings.h\"\n\n#include <constants.h>\n\n#include <classes/project_settings.hpp>\n\nusing namespace godot;\n\nvoid FmodGeneralSettings::set_channel_count(const int p_channel_count) {\n    _channel_count = p_channel_count;\n}\n\nint FmodGeneralSettings::get_channel_count() const {\n    return _channel_count;\n}\n\nvoid FmodGeneralSettings::set_is_live_update_enabled(const bool p_enable_live_update) {\n    _is_live_update_enabled = p_enable_live_update;\n}\n\nbool FmodGeneralSettings::get_is_live_update_enabled() const {\n    return _is_live_update_enabled;\n}\n\nvoid FmodGeneralSettings::set_is_memory_tracking_enabled(const bool p_enable_memory_tracking) {\n    _is_memory_tracking_enabled = p_enable_memory_tracking;\n}\n\nbool FmodGeneralSettings::get_is_memory_tracking_enabled() const {\n    return _is_memory_tracking_enabled;\n}\n\nvoid FmodGeneralSettings::set_default_listener_count(int p_listener_count) {\n    _default_listener_count = p_listener_count;\n}\n\nint FmodGeneralSettings::get_default_listener_count() const {\n    return _default_listener_count;\n}\n\nvoid FmodGeneralSettings::set_banks_path(const String& p_paths) {\n    _banks_path = p_paths;\n}\n\nconst String& FmodGeneralSettings::get_banks_path() const {\n    return _banks_path;\n}\n\nvoid FmodGeneralSettings::set_should_load_by_name(const bool p_should_load_by_name) {\n    _should_load_by_name = p_should_load_by_name;\n}\n\nbool FmodGeneralSettings::get_should_load_by_name() const {\n    return _should_load_by_name;\n}\n\nRef<FmodGeneralSettings> FmodGeneralSettings::get_from_project_settings() {\n    Ref<FmodGeneralSettings> settings;\n    settings.instantiate();\n\n    ProjectSettings* project_settings = ProjectSettings::get_singleton();\n\n    String channel_count_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, INITIALIZE_BASE_PATH, CHANNEL_COUNT_OPTION);\n    settings->set_channel_count(project_settings->get_setting(channel_count_setting_path, DEFAULT_CHANNEL_COUNT));\n\n    String is_liveupdate_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, INITIALIZE_BASE_PATH, IS_LIVE_UPDATE_ENABLED_OPTION);\n    settings->set_is_live_update_enabled(project_settings->get_setting(is_liveupdate_setting_path, DEFAULT_IS_LIVEUPDATE));\n\n    String is_memory_tracking_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, INITIALIZE_BASE_PATH, IS_LIVE_MEMORY_TRACKING_ENABLED_OPTION);\n    settings->set_is_memory_tracking_enabled(project_settings->get_setting(is_memory_tracking_setting_path, DEFAULT_IS_MEMORY_TRACKING));\n\n    String default_listener_count_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, INITIALIZE_BASE_PATH, DEFAULT_LISTENER_COUNT_OPTION);\n    settings->set_default_listener_count(project_settings->get_setting(default_listener_count_setting_path, DEFAULT_DEFAULT_LISTENER_COUNT));\n\n    String banks_paths_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, INITIALIZE_BASE_PATH, BANKS_PATH_OPTION);\n    settings->set_banks_path(project_settings->get_setting(banks_paths_setting_path, DEFAULT_BANKS_PATH));\n\n    String should_load_by_name_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, INITIALIZE_BASE_PATH, SHOULD_LOAD_BY_NAME);\n    settings->set_should_load_by_name(project_settings->get_setting(should_load_by_name_setting_path, DEFAULT_SHOULD_LOAD_BY_NAME));\n\n    return settings;\n}\n\nvoid FmodGeneralSettings::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"set_channel_count\", \"p_channel_count\"), &FmodGeneralSettings::set_channel_count);\n    ClassDB::bind_method(D_METHOD(\"get_channel_count\"), &FmodGeneralSettings::get_channel_count);\n\n    ClassDB::bind_method(D_METHOD(\"set_is_live_update_enabled\", \"p_enable_live_update\"), &FmodGeneralSettings::set_is_live_update_enabled);\n    ClassDB::bind_method(D_METHOD(\"get_is_live_update_enabled\"), &FmodGeneralSettings::get_is_live_update_enabled);\n\n    ClassDB::bind_method(D_METHOD(\"set_is_memory_tracking_enabled\", \"p_enable_memory_tracking\"), &FmodGeneralSettings::set_is_memory_tracking_enabled);\n    ClassDB::bind_method(D_METHOD(\"get_is_memory_tracking_enabled\"), &FmodGeneralSettings::get_is_memory_tracking_enabled);\n\n    ClassDB::bind_method(D_METHOD(\"set_default_listener_count\", \"p_listener_count\"), &FmodGeneralSettings::set_default_listener_count);\n    ClassDB::bind_method(D_METHOD(\"get_default_listener_count\"), &FmodGeneralSettings::get_default_listener_count);\n\n    ClassDB::bind_method(D_METHOD(\"set_banks_path\", \"p_paths\"), &FmodGeneralSettings::set_banks_path);\n    ClassDB::bind_method(D_METHOD(\"get_banks_path\"), &FmodGeneralSettings::get_banks_path);\n\n    ClassDB::bind_method(D_METHOD(\"set_should_load_by_name\", \"p_should_load_by_name\"), &FmodGeneralSettings::set_should_load_by_name);\n    ClassDB::bind_method(D_METHOD(\"get_should_load_by_name\"), &FmodGeneralSettings::get_should_load_by_name);\n\n    ADD_PROPERTY(\n      PropertyInfo(\n        Variant::INT,\n        \"channel_count\",\n        PROPERTY_HINT_NONE,\n        \"\",\n        PROPERTY_USAGE_DEFAULT\n      ),\n      \"set_channel_count\",\n      \"get_channel_count\"\n    );\n\n    ADD_PROPERTY(\n      PropertyInfo(\n        Variant::BOOL,\n        \"is_live_update_enabled\",\n        PROPERTY_HINT_NONE,\n        \"\",\n        PROPERTY_USAGE_DEFAULT\n      ),\n      \"set_is_live_update_enabled\",\n      \"get_is_live_update_enabled\"\n    );\n\n    ADD_PROPERTY(\n      PropertyInfo(\n        Variant::BOOL,\n        \"is_memory_tracking_enabled\",\n        PROPERTY_HINT_NONE,\n        \"\",\n        PROPERTY_USAGE_DEFAULT\n      ),\n      \"set_is_memory_tracking_enabled\",\n      \"get_is_memory_tracking_enabled\"\n    );\n\n    ADD_PROPERTY(\n      PropertyInfo(\n        Variant::INT,\n        \"default_listener_count\",\n        PROPERTY_HINT_NONE,\n        \"\",\n        PROPERTY_USAGE_DEFAULT\n      ),\n      \"set_default_listener_count\",\n      \"get_default_listener_count\"\n    );\n\n    ADD_PROPERTY(\n      PropertyInfo(\n        Variant::STRING,\n        \"banks_path\",\n        PROPERTY_HINT_NONE,\n        \"\",\n        PROPERTY_USAGE_DEFAULT\n      ),\n      \"set_banks_path\",\n      \"get_banks_path\"\n    );\n\n    ADD_PROPERTY(\n      PropertyInfo(\n        Variant::BOOL,\n        \"should_load_by_name\",\n        PROPERTY_HINT_NONE,\n        \"\",\n        PROPERTY_USAGE_DEFAULT\n      ),\n      \"set_should_load_by_name\",\n      \"get_should_load_by_name\"\n    );\n}\n"
  },
  {
    "path": "src/resources/fmod_settings.h",
    "content": "#ifndef GODOTFMOD_FMOD_SETTINGS_H\n#define GODOTFMOD_FMOD_SETTINGS_H\n\n#include \"fmod_studio.h\"\n\n#include <classes/resource.hpp>\n\nnamespace godot {\n    class FmodGeneralSettings : public Resource {\n        GDCLASS(FmodGeneralSettings, Resource)\n    public:\n        void set_channel_count(const int p_channel_count);\n        int get_channel_count() const;\n\n        void set_is_live_update_enabled(const bool p_enable_live_update);\n        bool get_is_live_update_enabled() const;\n\n        void set_is_memory_tracking_enabled(const bool p_enable_memory_tracking);\n        bool get_is_memory_tracking_enabled() const;\n\n        void set_default_listener_count(int p_listener_count);\n        int get_default_listener_count() const;\n\n        void set_banks_path(const String& p_paths);\n        const String& get_banks_path() const;\n\n        void set_should_load_by_name(const bool p_should_load_by_name);\n        bool get_should_load_by_name() const;\n\n        static Ref<FmodGeneralSettings> get_from_project_settings();\n\n        static constexpr const char* INITIALIZE_BASE_PATH = \"General\";\n        static constexpr const char* CHANNEL_COUNT_OPTION = \"channel_count\";\n        static constexpr const char* IS_LIVE_UPDATE_ENABLED_OPTION = \"is_live_update_enabled\";\n        static constexpr const char* IS_LIVE_MEMORY_TRACKING_ENABLED_OPTION = \"is_memory_tracking_enabled\";\n        static constexpr const char* DEFAULT_LISTENER_COUNT_OPTION = \"default_listener_count\";\n        static constexpr const char* BANKS_PATH_OPTION = \"banks_path\";\n        static constexpr const char* SHOULD_LOAD_BY_NAME = \"should_load_by_name\";\n\n        static constexpr const int DEFAULT_CHANNEL_COUNT = 1024;\n        static constexpr const bool DEFAULT_IS_LIVEUPDATE = true;\n        static constexpr const bool DEFAULT_IS_MEMORY_TRACKING = false;\n        static constexpr const int DEFAULT_DEFAULT_LISTENER_COUNT = 1;\n        static constexpr const char* DEFAULT_BANKS_PATH = \"res://\";\n        static constexpr const bool DEFAULT_SHOULD_LOAD_BY_NAME = false;\n\n    private:\n        int _channel_count;\n        int _default_listener_count;\n        bool _is_live_update_enabled;\n        bool _is_memory_tracking_enabled;\n        String _banks_path;\n        bool _should_load_by_name;\n\n    protected:\n        static void _bind_methods();\n\n    };\n}\n\n#endif// GODOTFMOD_FMOD_SETTINGS_H\n"
  },
  {
    "path": "src/resources/fmod_software_format_settings.cpp",
    "content": "#include \"fmod_software_format_settings.h\"\n\n#include <constants.h>\n\n#include <classes/project_settings.hpp>\n\nusing namespace godot;\n\nvoid FmodSoftwareFormatSettings::set_sample_rate(const int p_sample_rate) {\n    _sample_rate = p_sample_rate;\n}\n\nint FmodSoftwareFormatSettings::get_sample_rate() const {\n    return _sample_rate;\n}\n\nvoid FmodSoftwareFormatSettings::set_speaker_mode(const int p_speaker_mode) {\n    _speaker_mode = p_speaker_mode;\n}\n\nint FmodSoftwareFormatSettings::get_speaker_mode() const {\n    return _speaker_mode;\n}\n\nvoid FmodSoftwareFormatSettings::set_raw_speakers_count(const int p_raw_speakers_count) {\n    _raw_speakers_count = p_raw_speakers_count;\n}\n\nint FmodSoftwareFormatSettings::get_raw_speakers_count() const {\n    return _raw_speakers_count;\n}\n\nRef<FmodSoftwareFormatSettings> FmodSoftwareFormatSettings::get_from_project_settings() {\n    Ref<FmodSoftwareFormatSettings> settings;\n    settings.instantiate();\n\n    ProjectSettings* project_settings = ProjectSettings::get_singleton();\n\n    String sample_rate_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, SOFTWARE_FORMAT_SETTINGS_BASE_PATH, SAMPLE_RATE_OPTION);\n    settings->set_sample_rate(project_settings->get_setting(sample_rate_setting_path, DEFAULT_SAMPLE_RATE));\n\n    String speaker_mode_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, SOFTWARE_FORMAT_SETTINGS_BASE_PATH, SPEAKER_MODE_OPTION);\n    settings->set_speaker_mode(project_settings->get_setting(speaker_mode_setting_path, DEFAULT_SPEAKER_MODE));\n\n    String raw_speaker_count_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, SOFTWARE_FORMAT_SETTINGS_BASE_PATH, RAW_SPEAKER_COUNT_OPTION);\n    settings->set_raw_speakers_count(project_settings->get_setting(raw_speaker_count_setting_path, DEFAULT_RAW_SPEAKER_COUNT));\n\n    return settings;\n}\n\nvoid FmodSoftwareFormatSettings::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"set_sample_rate\", \"p_sample_rate\"), &FmodSoftwareFormatSettings::set_sample_rate);\n    ClassDB::bind_method(D_METHOD(\"get_sample_rate\"), &FmodSoftwareFormatSettings::get_sample_rate);\n\n    ClassDB::bind_method(D_METHOD(\"set_speaker_mode\", \"p_speaker_mode\"), &FmodSoftwareFormatSettings::set_speaker_mode);\n    ClassDB::bind_method(D_METHOD(\"get_speaker_mode\"), &FmodSoftwareFormatSettings::get_speaker_mode);\n\n    ClassDB::bind_method(D_METHOD(\"set_raw_speakers_count\", \"p_raw_speakers_count\"), &FmodSoftwareFormatSettings::set_raw_speakers_count);\n    ClassDB::bind_method(D_METHOD(\"get_raw_speakers_count\"), &FmodSoftwareFormatSettings::get_raw_speakers_count);\n\n    ADD_PROPERTY(\n      PropertyInfo(\n        Variant::INT,\n        \"sample_rate\",\n        PROPERTY_HINT_NONE,\n        \"\",\n        PROPERTY_USAGE_DEFAULT\n      ),\n      \"set_sample_rate\",\n      \"get_sample_rate\"\n    );\n\n    ADD_PROPERTY(\n      PropertyInfo(\n        Variant::INT,\n        \"speaker_mode\",\n        PROPERTY_HINT_ENUM,\n        \"\",\n        PROPERTY_USAGE_DEFAULT\n      ),\n      \"set_speaker_mode\",\n      \"get_speaker_mode\"\n    );\n\n    ADD_PROPERTY(\n      PropertyInfo(\n        Variant::INT,\n        \"raw_speakers_count\",\n        PROPERTY_HINT_NONE,\n        \"\",\n        PROPERTY_USAGE_DEFAULT\n      ),\n      \"set_raw_speakers_count\",\n      \"get_raw_speakers_count\"\n    );\n}\n"
  },
  {
    "path": "src/resources/fmod_software_format_settings.h",
    "content": "#ifndef GODOTFMOD_FMOD_SOFTWARE_FORMAT_SETTINGS_H\n#define GODOTFMOD_FMOD_SOFTWARE_FORMAT_SETTINGS_H\n\n#include \"fmod_common.h\"\n\n#include <classes/resource.hpp>\n\nnamespace godot {\n    class FmodSoftwareFormatSettings : public Resource {\n        GDCLASS(FmodSoftwareFormatSettings, Resource)\n\n    public:\n        void set_sample_rate(const int p_sample_rate);\n        int get_sample_rate() const;\n\n        void set_speaker_mode(const int p_speaker_mode);\n        int get_speaker_mode() const;\n\n        void set_raw_speakers_count(const int p_raw_speakers_count);\n        int get_raw_speakers_count() const;\n\n        static Ref<FmodSoftwareFormatSettings> get_from_project_settings();\n\n        FmodSoftwareFormatSettings() = default;\n        ~FmodSoftwareFormatSettings() = default;\n\n        static constexpr const char* SOFTWARE_FORMAT_SETTINGS_BASE_PATH = \"Software Format\";\n\n        static constexpr const char* SAMPLE_RATE_OPTION = \"sample_rate\";\n        static constexpr const char* SPEAKER_MODE_OPTION = \"speaker_mode\";\n        static constexpr const char* RAW_SPEAKER_COUNT_OPTION = \"raw_speaker_count\";\n\n        static constexpr const int DEFAULT_SAMPLE_RATE = 48000;\n        static constexpr const int DEFAULT_SPEAKER_MODE = FMOD_SPEAKERMODE::FMOD_SPEAKERMODE_STEREO;\n        static constexpr const int DEFAULT_RAW_SPEAKER_COUNT = 0;\n\n    private:\n        int _sample_rate;\n        int _speaker_mode;\n        int _raw_speakers_count;\n\n    protected:\n        static void _bind_methods();\n    };\n}\n\n#endif// GODOTFMOD_FMOD_SOFTWARE_FORMAT_SETTINGS_H\n"
  },
  {
    "path": "src/resources/fmod_sound_3d_settings.cpp",
    "content": "#include \"fmod_sound_3d_settings.h\"\n\n#include <constants.h>\n\n#include <classes/project_settings.hpp>\n\nusing namespace godot;\n\nvoid FmodSound3DSettings::set_doppler_scale(const float p_doppler_scale) {\n    _doppler_scale = p_doppler_scale;\n}\n\nfloat FmodSound3DSettings::get_doppler_scale() const {\n    return _doppler_scale;\n}\n\nvoid FmodSound3DSettings::set_distance_factor(const float p_distance_factor) {\n    _distance_factor = p_distance_factor;\n}\n\nfloat FmodSound3DSettings::get_distance_factor() const {\n    return _distance_factor;\n}\n\nvoid FmodSound3DSettings::set_rolloff_scale(const float p_rolloff_scale) {\n    _rolloff_scale = p_rolloff_scale;\n}\n\nfloat FmodSound3DSettings::get_rolloff_scale() const {\n    return _rolloff_scale;\n}\n\nRef<FmodSound3DSettings> FmodSound3DSettings::get_from_project_settings() {\n    Ref<FmodSound3DSettings> settings;\n    settings.instantiate();\n\n    ProjectSettings* project_settings = ProjectSettings::get_singleton();\n\n    String doppler_scale_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, THREE_D_SETTINGS_BASE_PATH, DOPPLER_SCALE_OPTION);\n    settings->set_doppler_scale(project_settings->get_setting(doppler_scale_setting_path, DEFAULT_DOPPLER_SCALE));\n\n    String distance_factor_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, THREE_D_SETTINGS_BASE_PATH, DISTANCE_FACTOR_OPTION);\n    settings->set_distance_factor(project_settings->get_setting(distance_factor_setting_path, DEFAULT_DISTANCE_FACTOR));\n\n    String rolloff_scale_setting_path = vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, THREE_D_SETTINGS_BASE_PATH, ROLLOFF_SCALE_OPTION);\n    settings->set_rolloff_scale(project_settings->get_setting(rolloff_scale_setting_path, DEFAULT_ROLLOFF_SCALE));\n\n    return settings;\n}\n\nvoid FmodSound3DSettings::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"set_doppler_scale\", \"p_doppler_scale\"), &FmodSound3DSettings::set_doppler_scale);\n    ClassDB::bind_method(D_METHOD(\"get_doppler_scale\"), &FmodSound3DSettings::get_doppler_scale);\n\n    ClassDB::bind_method(D_METHOD(\"set_distance_factor\", \"p_distance_factor\"), &FmodSound3DSettings::set_distance_factor);\n    ClassDB::bind_method(D_METHOD(\"get_distance_factor\"), &FmodSound3DSettings::get_distance_factor);\n\n    ClassDB::bind_method(D_METHOD(\"set_rolloff_scale\", \"p_rolloff_scale\"), &FmodSound3DSettings::set_rolloff_scale);\n    ClassDB::bind_method(D_METHOD(\"get_rolloff_scale\"), &FmodSound3DSettings::get_rolloff_scale);\n\n    ADD_PROPERTY(\n      PropertyInfo(\n        Variant::FLOAT,\n        \"doppler_scale\",\n        PROPERTY_HINT_NONE,\n        \"\",\n        PROPERTY_USAGE_DEFAULT\n      ),\n      \"set_doppler_scale\",\n      \"get_doppler_scale\"\n    );\n\n    ADD_PROPERTY(\n      PropertyInfo(\n        Variant::FLOAT,\n        \"distance_factor\",\n        PROPERTY_HINT_NONE,\n        \"\",\n        PROPERTY_USAGE_DEFAULT\n      ),\n      \"set_distance_factor\",\n      \"get_distance_factor\"\n    );\n\n    ADD_PROPERTY(\n      PropertyInfo(\n        Variant::FLOAT,\n        \"rolloff_scale\",\n        PROPERTY_HINT_NONE,\n        \"\",\n        PROPERTY_USAGE_DEFAULT\n      ),\n      \"set_rolloff_scale\",\n      \"get_rolloff_scale\"\n    );\n}"
  },
  {
    "path": "src/resources/fmod_sound_3d_settings.h",
    "content": "#ifndef GODOTFMOD_FMOD_SOUND_3D_SETTINGS_H\n#define GODOTFMOD_FMOD_SOUND_3D_SETTINGS_H\n\n#include <classes/resource.hpp>\n\nnamespace godot {\n    class FmodSound3DSettings : public Resource {\n        GDCLASS(FmodSound3DSettings, Resource)\n\n    public:\n        void set_doppler_scale(const float p_doppler_scale);\n        float get_doppler_scale() const;\n\n        void set_distance_factor(const float p_distance_factor);\n        float get_distance_factor() const;\n\n        void set_rolloff_scale(const float p_rolloff_scale);\n        float get_rolloff_scale() const;\n\n        static Ref<FmodSound3DSettings> get_from_project_settings();\n\n        FmodSound3DSettings() = default;\n        ~FmodSound3DSettings() = default;\n\n        static constexpr const char* THREE_D_SETTINGS_BASE_PATH = \"3D Settings\";\n\n        static constexpr const char* DOPPLER_SCALE_OPTION = \"doppler_scale\";\n        static constexpr const char* DISTANCE_FACTOR_OPTION = \"distance_factor\";\n        static constexpr const char* ROLLOFF_SCALE_OPTION = \"rolloff_scale\";\n\n        static constexpr const float DEFAULT_DOPPLER_SCALE = 1;\n        static constexpr const float DEFAULT_DISTANCE_FACTOR = 1;\n        static constexpr const float DEFAULT_ROLLOFF_SCALE = 1;\n\n    private:\n        float _doppler_scale;\n        float _distance_factor;\n        float _rolloff_scale;\n\n    protected:\n        static void _bind_methods();\n    };\n}\n\n#endif// GODOTFMOD_FMOD_SOUND_3D_SETTINGS_H\n"
  },
  {
    "path": "src/studio/fmod_bank.cpp",
    "content": "#include \"fmod_bank.h\"\n\n#include \"helpers/common.h\"\n#include \"fmod_server.h\"\n\nusing namespace godot;\n\nvoid FmodBank::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"get_loading_state\"), &FmodBank::get_loading_state);\n    ClassDB::bind_method(D_METHOD(\"get_bus_count\"), &FmodBank::get_bus_count);\n    ClassDB::bind_method(D_METHOD(\"get_event_description_count\"), &FmodBank::get_event_description_count);\n    ClassDB::bind_method(D_METHOD(\"get_string_count\"), &FmodBank::get_string_count);\n    ClassDB::bind_method(D_METHOD(\"get_VCA_count\"), &FmodBank::get_vca_count);\n    ClassDB::bind_method(D_METHOD(\"get_description_list\"), &FmodBank::get_description_list);\n    ClassDB::bind_method(D_METHOD(\"get_bus_list\"), &FmodBank::get_bus_list);\n    ClassDB::bind_method(D_METHOD(\"get_vca_list\"), &FmodBank::get_vca_list);\n    ClassDB::bind_method(D_METHOD(\"is_valid\"), &FmodBank::is_valid);\n    ClassDB::bind_method(D_METHOD(\"get_godot_res_path\"), &FmodBank::get_godot_res_path);\n    ClassDB::bind_method(D_METHOD(\"get_path\"), &FmodBank::get_path);\n    ClassDB::bind_method(D_METHOD(\"get_guid\"), &FmodBank::get_guid_as_string);\n}\n\nint FmodBank::get_loading_state() {\n    FMOD_STUDIO_LOADING_STATE state;\n\n    ERROR_CHECK_WITH_REASON(_wrapped->getLoadingState(&state), vformat(\"Cannot get loading state for bank %s\", get_path()));\n    return state;\n}\n\nint64_t FmodBank::get_bus_count() {\n    return _buses.size();\n}\n\nint64_t FmodBank::get_event_description_count() {\n    return _event_descriptions.size();\n}\n\nint64_t FmodBank::get_vca_count() const {\n    return _vcas.size();\n}\n\nint FmodBank::get_string_count() const {\n    int count = -1;\n    ERROR_CHECK_WITH_REASON(_wrapped->getStringCount(&count), vformat(\"Cannot get string count for bank %s\", get_path()));\n    return count;\n}\n\nArray FmodBank::get_description_list() const {\n    Array array;\n    for (const Ref<FmodEventDescription>& ref : _event_descriptions) {\n        array.append(ref);\n    }\n    return array;\n}\n\nArray FmodBank::get_bus_list() const {\n    Array array;\n    for (const Ref<FmodBus>& ref : _buses) {\n        array.append(ref);\n    }\n    return array;\n}\n\nArray FmodBank::get_vca_list() const {\n    Array array;\n    for (const Ref<FmodVCA>& ref : _vcas) {\n        array.append(ref);\n    }\n    return array;\n}\n\nvoid FmodBank::update_bank_data() {\n    load_all_buses();\n    load_all_vca();\n    load_all_event_descriptions();\n}\n\nvoid FmodBank::load_all_vca() {\n    int size = 0;\n    if (ERROR_CHECK_WITH_REASON(_wrapped->getVCACount(&size), vformat(\"Cannot get VCA count for bank %s\", get_path()))) {\n        if (size == 0) {\n            return;\n        }\n\n        Vector<FMOD::Studio::VCA*> raw_vcas;\n        raw_vcas.resize(size);\n\n        if (ERROR_CHECK_WITH_REASON(_wrapped->getVCAList(raw_vcas.ptrw(), size, &size), vformat(\"Cannot get VCA list for bank %s\", get_path()))) {\n            _vcas.clear();\n\n            for (int i = 0; i < size; ++i) {\n                Ref<FmodVCA> ref = FmodVCA::create_ref(raw_vcas[i]);\n                _vcas.push_back(ref);\n            }\n        }\n    }\n}\n\nvoid FmodBank::load_all_buses() {\n    int size = 0;\n    if (ERROR_CHECK_WITH_REASON(_wrapped->getBusCount(&size), vformat(\"Cannot get bus count for bank %s\", get_path()))) {\n        if (size == 0) {\n            return;\n        }\n\n        Vector<FMOD::Studio::Bus*> raw_buses;\n        raw_buses.resize(size);\n\n        if (ERROR_CHECK_WITH_REASON(_wrapped->getBusList(raw_buses.ptrw(), size, &size), vformat(\"Cannot get bus list for bank %s\", get_path()))) {\n            _buses.clear();\n\n            for (int i = 0; i < size; ++i) {\n                Ref<FmodBus> ref = FmodBus::create_ref(raw_buses[i]);\n                _buses.push_back(ref);\n            }\n        }\n    }\n}\n\nvoid FmodBank::load_all_event_descriptions() {\n    int size = 0;\n    if (ERROR_CHECK_WITH_REASON(_wrapped->getEventCount(&size), vformat(\"Cannot get event count for bank %s\", get_path()))) {\n        if (size == 0) {\n            return;\n        }\n\n        Vector<FMOD::Studio::EventDescription*> raw_events;\n        raw_events.resize(size);\n\n        if (ERROR_CHECK_WITH_REASON(_wrapped->getEventList(raw_events.ptrw(), size, &size), vformat(\"Cannot get event list for bank %s\", get_path()))) {\n            _event_descriptions.clear();\n\n            for (int i = 0; i < size; ++i) {\n                Ref<FmodEventDescription> ref = FmodEventDescription::create_ref(raw_events[i]);\n                _event_descriptions.push_back(ref);\n            }\n        }\n    }\n}\n\nconst List<Ref<FmodEventDescription>>& FmodBank::get_event_descriptions() const {\n    return _event_descriptions;\n}\n\nconst List<Ref<FmodBus>>& FmodBank::get_buses() const {\n    return _buses;\n}\n\nconst List<Ref<FmodVCA>>& FmodBank::get_vcas() const {\n    return _vcas;\n}\n\nconst String& FmodBank::get_godot_res_path() const {\n    return _godot_res_path;\n}\n\nFmodBank::~FmodBank() {\n    FmodServer::get_singleton()->unload_bank(_godot_res_path);\n}\n"
  },
  {
    "path": "src/studio/fmod_bank.h",
    "content": "#ifndef GODOTFMOD_FMOD_BANK_H\n#define GODOTFMOD_FMOD_BANK_H\n\n#include \"classes/ref_counted.hpp\"\n#include \"fmod_bus.h\"\n#include \"fmod_event_description.h\"\n#include \"fmod_studio.hpp\"\n#include \"fmod_vca.h\"\n#include \"helpers/common.h\"\n\nnamespace godot {\n    class FmodBank : public RefCounted {\n        FMODCLASSWITHPATH(FmodBank, RefCounted, FMOD::Studio::Bank);\n\n        List<Ref<FmodEventDescription>> _event_descriptions;\n        List<Ref<FmodBus>> _buses;\n        List<Ref<FmodVCA>> _vcas;\n        String _godot_res_path;\n\n        void load_all_vca();\n        void load_all_buses();\n        void load_all_event_descriptions();\n\n    public:\n        FmodBank() = default;\n        ~FmodBank() override;\n\n        int get_loading_state();\n\n        int64_t get_event_description_count();\n        int64_t get_bus_count();\n        int64_t get_vca_count() const;\n        int get_string_count() const;\n\n        Array get_description_list() const;\n        Array get_bus_list() const;\n        Array get_vca_list() const;\n\n        void update_bank_data();\n\n        const List<Ref<FmodEventDescription>>& get_event_descriptions() const;\n        const List<Ref<FmodBus>>& get_buses() const;\n        const List<Ref<FmodVCA>>& get_vcas() const;\n\n        const String& get_godot_res_path() const;\n\n        inline static Ref<FmodBank> create_ref(FMOD::Studio::Bank* wrapped, const String& p_godot_res_path) {\n            Ref<FmodBank> ref { create_ref(wrapped) };\n            ref->_godot_res_path = p_godot_res_path;\n            return ref;\n        }\n\n    protected:\n        static void _bind_methods();\n    };\n}// namespace godot\n#endif// GODOTFMOD_FMOD_BANK_H\n"
  },
  {
    "path": "src/studio/fmod_bus.cpp",
    "content": "#include \"fmod_bus.h\"\n\n#include \"helpers/common.h\"\n\nusing namespace godot;\n\nvoid FmodBus::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"get_mute\"), &FmodBus::get_mute);\n    ClassDB::bind_method(D_METHOD(\"get_paused\"), &FmodBus::get_paused);\n    ClassDB::bind_method(D_METHOD(\"get_volume\"), &FmodBus::get_volume);\n    ClassDB::bind_method(D_METHOD(\"set_mute\", \"mute\"), &FmodBus::set_mute);\n    ClassDB::bind_method(D_METHOD(\"set_paused\", \"paused\"), &FmodBus::set_paused);\n    ClassDB::bind_method(D_METHOD(\"set_volume\", \"volume\"), &FmodBus::set_volume);\n    ClassDB::bind_method(D_METHOD(\"stop_all_events\", \"stopMode\"), &FmodBus::stop_all_events);\n    ClassDB::bind_method(D_METHOD(\"is_valid\"), &FmodBus::is_valid);\n\n    ClassDB::bind_method(D_METHOD(\"get_path\"), &FmodBus::get_path);\n    ClassDB::bind_method(D_METHOD(\"get_guid\"), &FmodBus::get_guid_as_string);\n\n    ADD_PROPERTY(PropertyInfo(Variant::BOOL, \"mute\",PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_NONE), \"set_mute\", \"get_mute\");\n    ADD_PROPERTY(PropertyInfo(Variant::BOOL, \"paused\",PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_NONE), \"set_paused\", \"get_paused\");\n    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, \"volume\",PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_NONE), \"set_volume\", \"get_volume\");\n}\n\nbool FmodBus::get_mute() const {\n    bool mute = false;\n    ERROR_CHECK_WITH_REASON(_wrapped->getMute(&mute), vformat(\"Cannot check mute for bus %s\", get_path()));\n    return mute;\n}\n\nbool FmodBus::get_paused() const {\n    bool paused = false;\n    ERROR_CHECK_WITH_REASON(_wrapped->getPaused(&paused), vformat(\"Cannot check paused for bus %s\", get_path()));\n    return paused;\n}\n\nfloat FmodBus::get_volume() const {\n    float volume = 0.0f;\n    ERROR_CHECK_WITH_REASON(_wrapped->getVolume(&volume), vformat(\"Cannot get volume for bus %s\", get_path()));\n    return volume;\n}\n\nvoid FmodBus::set_mute(bool mute) const {\n    ERROR_CHECK_WITH_REASON(_wrapped->setMute(mute), vformat(\"Cannot mute bus %s\", get_path()));\n}\n\nvoid FmodBus::set_paused(bool paused) const {\n    ERROR_CHECK_WITH_REASON(_wrapped->setPaused(paused), vformat(\"Cannot pause bus %s\", get_path()));\n}\n\nvoid FmodBus::set_volume(float volume) const {\n    ERROR_CHECK_WITH_REASON(_wrapped->setVolume(volume), vformat(\"Cannot set bus %s volume to %f\", get_path(), volume));\n}\n\nvoid FmodBus::stop_all_events(int stopMode) {\n    ERROR_CHECK_WITH_REASON(_wrapped->stopAllEvents(static_cast<FMOD_STUDIO_STOP_MODE>(stopMode)),\n                            vformat(\"Cannot stop all bus %s events\", get_path()));\n}"
  },
  {
    "path": "src/studio/fmod_bus.h",
    "content": "#ifndef GODOTFMOD_FMOD_BUS_H\n#define GODOTFMOD_FMOD_BUS_H\n\n#include \"fmod_studio.hpp\"\n#include \"helpers/common.h\"\n\nnamespace godot {\n    class FmodBus : public RefCounted {\n        FMODCLASSWITHPATH(FmodBus, RefCounted, FMOD::Studio::Bus);\n\n    public:\n        FmodBus() = default;\n        ~FmodBus() override = default;\n\n        bool get_mute() const;\n        bool get_paused() const;\n        float get_volume() const;\n        void set_mute(bool mute) const;\n        void set_paused(bool paused) const;\n        void set_volume(float volume) const;\n        void stop_all_events(int stopMode);\n\n        protected:\n            static void _bind_methods();\n    };\n}// namespace godot\n#endif// GODOTFMOD_FMOD_BUS_H\n"
  },
  {
    "path": "src/studio/fmod_event.cpp",
    "content": "#include \"fmod.h\"\n#include \"fmod_server.h\"\n#include \"helpers/common.h\"\n#include \"helpers/maths.h\"\n#include \"fmod_event.h\"\n\nusing namespace godot;\n\nvoid FmodEvent::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"get_parameter_by_name\", \"parameter_name\"), &FmodEvent::get_parameter_by_name);\n    ClassDB::bind_method(D_METHOD(\"set_parameter_by_name\", \"parameter_name\", \"value\"), &FmodEvent::set_parameter_by_name);\n    ClassDB::bind_method(D_METHOD(\"set_parameter_by_name_with_label\", \"parameter_name\", \"label\", \"ignoreseekspeed\"), &FmodEvent::set_parameter_by_name_with_label);\n    ClassDB::bind_method(D_METHOD(\"get_parameter_by_id\", \"parameter_id\"), &FmodEvent::get_parameter_by_id);\n    ClassDB::bind_method(D_METHOD(\"set_parameter_by_id\", \"parameter_id\", \"value\"), &FmodEvent::set_parameter_by_id);\n    ClassDB::bind_method(D_METHOD(\"set_parameter_by_id_with_label\", \"parameter_id\", \"label\", \"ignoreseekspeed\"), &FmodEvent::set_parameter_by_id_with_label);\n    ClassDB::bind_method(D_METHOD(\"start\"), &FmodEvent::start);\n    ClassDB::bind_method(D_METHOD(\"stop\", \"stopMode\"), &FmodEvent::stop);\n    ClassDB::bind_method(D_METHOD(\"event_key_off\"), &FmodEvent::event_key_off);\n    ClassDB::bind_method(D_METHOD(\"get_playback_state\"), &FmodEvent::get_playback_state);\n    ClassDB::bind_method(D_METHOD(\"get_paused\"), &FmodEvent::get_paused);\n    ClassDB::bind_method(D_METHOD(\"set_paused\", \"paused\"), &FmodEvent::set_paused);\n    ClassDB::bind_method(D_METHOD(\"get_pitch\"), &FmodEvent::get_pitch);\n    ClassDB::bind_method(D_METHOD(\"set_pitch\", \"pitch\"), &FmodEvent::set_pitch);\n    ClassDB::bind_method(D_METHOD(\"get_volume\"), &FmodEvent::get_volume);\n    ClassDB::bind_method(D_METHOD(\"set_volume\", \"volume\"), &FmodEvent::set_volume);\n    ClassDB::bind_method(D_METHOD(\"get_timeline_position\"), &FmodEvent::get_timeline_position);\n    ClassDB::bind_method(D_METHOD(\"set_timeline_position\", \"position\"), &FmodEvent::set_timeline_position);\n    ClassDB::bind_method(D_METHOD(\"get_reverb_level\", \"index\"), &FmodEvent::get_reverb_level);\n    ClassDB::bind_method(D_METHOD(\"set_reverb_level\", \"index\", \"level\"), &FmodEvent::set_reverb_level);\n    ClassDB::bind_method(D_METHOD(\"is_virtual\"), &FmodEvent::is_virtual);\n    ClassDB::bind_method(D_METHOD(\"set_listener_mask\", \"mask\"), &FmodEvent::set_listener_mask);\n    ClassDB::bind_method(D_METHOD(\"get_listener_mask\"), &FmodEvent::get_listener_mask);\n    ClassDB::bind_method(D_METHOD(\"set_2d_attributes\", \"position\"), &FmodEvent::set_2d_attributes);\n    ClassDB::bind_method(D_METHOD(\"get_2d_attributes\"), &FmodEvent::get_2d_attributes);\n    ClassDB::bind_method(D_METHOD(\"set_3d_attributes\", \"transform\"), &FmodEvent::set_3d_attributes);\n    ClassDB::bind_method(D_METHOD(\"get_3d_attributes\"), &FmodEvent::get_3d_attributes);\n    ClassDB::bind_method(D_METHOD(\"set_node_attributes\", \"transform\"), &FmodEvent::set_node_attributes);\n    ClassDB::bind_method(D_METHOD(\"set_callback\", \"callback\", \"callbackMask\"), &FmodEvent::set_callback);\n    ClassDB::bind_method(D_METHOD(\"set_programmer_callback\", \"p_programmers_callback_sound_key\"), &FmodEvent::set_programmer_callback);\n    ClassDB::bind_method(D_METHOD(\"get_programmer_callback_sound_key\"), &FmodEvent::get_programmers_callback_sound_key);\n    ClassDB::bind_method(D_METHOD(\"is_valid\"), &FmodEvent::is_valid);\n    ClassDB::bind_method(D_METHOD(\"release\"), &FmodEvent::release);\n\n    ADD_PROPERTY(PropertyInfo(Variant::BOOL, \"paused\",PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_NONE), \"set_paused\", \"get_paused\");\n    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, \"pitch\",PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_NONE), \"set_pitch\", \"get_pitch\");\n    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, \"volume\",PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_NONE), \"set_volume\", \"get_volume\");\n    ADD_PROPERTY(PropertyInfo(Variant::INT, \"position\",PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_NONE), \"set_timeline_position\", \"get_timeline_position\");\n    ADD_PROPERTY(PropertyInfo(Variant::INT, \"listener_mask\",PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_NONE), \"set_listener_mask\", \"get_listener_mask\");\n    ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, \"transform_2d\",PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_NONE), \"set_2d_attributes\", \"get_2d_attributes\");\n    ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, \"transform_3d\",PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_NONE), \"set_node_attributes\", \"get_3d_attributes\");\n\n    BIND_ENUM_CONSTANT(FMOD_STUDIO_PLAYBACK_PLAYING);\n    BIND_ENUM_CONSTANT(FMOD_STUDIO_PLAYBACK_SUSTAINING);\n    BIND_ENUM_CONSTANT(FMOD_STUDIO_PLAYBACK_STOPPED);\n    BIND_ENUM_CONSTANT(FMOD_STUDIO_PLAYBACK_STARTING);\n    BIND_ENUM_CONSTANT(FMOD_STUDIO_PLAYBACK_STOPPING);\n    BIND_ENUM_CONSTANT(FMOD_STUDIO_PLAYBACK_FORCEINT);\n}\n\nfloat FmodEvent::get_parameter_by_name(const String& parameter_name) const {\n    float p = -1;\n    ERROR_CHECK_WITH_REASON(_wrapped->getParameterByName(parameter_name.utf8().get_data(), &p),\n                            vformat(\"Cannot get parameter %s\", parameter_name));\n    return p;\n}\n\nvoid FmodEvent::set_parameter_by_name(const String& parameter_name, float value) const {\n    ERROR_CHECK_WITH_REASON(_wrapped->setParameterByName(parameter_name.utf8().get_data(), value),\n                            vformat(\"Cannot set parameter %s to value %f\", parameter_name, value));\n}\n\nvoid FmodEvent::set_parameter_by_name_with_label(const String& parameter_name, const String& label, bool ignoreseekspeed) const {\n    ERROR_CHECK_WITH_REASON(_wrapped->setParameterByNameWithLabel(parameter_name.utf8().get_data(), label.utf8().get_data(), ignoreseekspeed),\n                            vformat(\"Cannot set parameter %s value to %s\", parameter_name, label));\n}\n\nfloat FmodEvent::get_parameter_by_id(uint64_t long_id) const {\n    return get_parameter_by_fmod_id(ulong_to_fmod_parameter_id(long_id));\n}\n\nfloat FmodEvent::get_parameter_by_fmod_id(const FMOD_STUDIO_PARAMETER_ID& parameter_id) const {\n    float value = -1.0f;\n    ERROR_CHECK_WITH_REASON(_wrapped->getParameterByID(parameter_id, &value),\n                            vformat(\"Cannot get parameter with id: %d\", fmod_parameter_id_to_ulong(parameter_id)));\n    return value;\n}\n\nvoid FmodEvent::set_parameter_by_id(uint64_t long_id, float value) const {\n    set_parameter_by_fmod_id(ulong_to_fmod_parameter_id(long_id), value);\n}\n\nvoid FmodEvent::set_parameter_by_fmod_id(const FMOD_STUDIO_PARAMETER_ID& parameter_id, float value) const {\n    ERROR_CHECK_WITH_REASON(_wrapped->setParameterByID(parameter_id, value),\n                            vformat(\"Cannot set parameter with id %d to value %f\", fmod_parameter_id_to_ulong(parameter_id), value));\n}\n\nvoid FmodEvent::set_parameter_by_fmod_id_with_label(const FMOD_STUDIO_PARAMETER_ID& parameter_id, const String& label, bool ignoreseekspeed) const {\n    ERROR_CHECK_WITH_REASON(_wrapped->setParameterByIDWithLabel(parameter_id, label.utf8().get_data(), ignoreseekspeed),\n                            vformat(\"Cannot set parameter to id %d to value %s\", fmod_parameter_id_to_ulong(parameter_id), label));\n}\n\nvoid FmodEvent::set_parameter_by_id_with_label(uint64_t parameter_id, const String& label, bool ignoreseekspeed) const {\n    set_parameter_by_fmod_id_with_label(ulong_to_fmod_parameter_id(parameter_id), label, ignoreseekspeed);\n}\n\nvoid FmodEvent::release() const {\n    ERROR_CHECK(_wrapped->release());\n}\n\nvoid FmodEvent::start() const {\n    ERROR_CHECK(_wrapped->start());\n}\n\nvoid FmodEvent::stop(int stopMode) const {\n    ERROR_CHECK(_wrapped->stop(static_cast<FMOD_STUDIO_STOP_MODE>(stopMode)));\n}\n\nvoid FmodEvent::event_key_off() const {\n    ERROR_CHECK(_wrapped->keyOff());\n}\n\nFMOD_STUDIO_PLAYBACK_STATE FmodEvent::get_playback_state() const {\n    FMOD_STUDIO_PLAYBACK_STATE playback_state;\n    ERROR_CHECK(_wrapped->getPlaybackState(&playback_state));\n    return playback_state;\n}\n\nbool FmodEvent::get_paused() const {\n    bool paused = false;\n    ERROR_CHECK(_wrapped->getPaused(&paused));\n    return paused;\n}\n\nvoid FmodEvent::set_paused(bool paused) const {\n    ERROR_CHECK(_wrapped->setPaused(paused));\n}\n\nfloat FmodEvent::get_pitch() const {\n    float pitch = 0.0f;\n    ERROR_CHECK(_wrapped->getPitch(&pitch));\n    return pitch;\n}\n\nvoid FmodEvent::set_pitch(float pitch) const {\n    ERROR_CHECK(_wrapped->setPitch(pitch));\n}\n\nfloat FmodEvent::get_volume() {\n    float volume = 0.0f;\n    ERROR_CHECK(_wrapped->getVolume(&volume));\n    return volume;\n}\n\nvoid FmodEvent::set_volume(float volume) const {\n    ERROR_CHECK(_wrapped->setVolume(volume));\n}\n\nint FmodEvent::get_timeline_position() const {\n    int tp = 0;\n    ERROR_CHECK(_wrapped->getTimelinePosition(&tp));\n    return tp;\n}\n\nvoid FmodEvent::set_timeline_position(int position) const {\n    ERROR_CHECK(_wrapped->setTimelinePosition(position));\n}\n\nfloat FmodEvent::get_reverb_level(int index) const {\n    float rvl = 0.0f;\n    ERROR_CHECK(_wrapped->getReverbLevel(index, &rvl));\n    return rvl;\n}\n\nvoid FmodEvent::set_reverb_level(int index, float level) const {\n    ERROR_CHECK(_wrapped->setReverbLevel(index, level));\n}\n\nbool FmodEvent::is_virtual() const {\n    bool v = false;\n    ERROR_CHECK(_wrapped->isVirtual(&v));\n    return v;\n}\n\nvoid FmodEvent::set_listener_mask(unsigned int mask) const {\n    ERROR_CHECK(_wrapped->setListenerMask(mask));\n}\n\nuint32_t FmodEvent::get_listener_mask() const {\n    uint32_t mask = 0;\n    ERROR_CHECK(_wrapped->getListenerMask(&mask));\n    return mask;\n}\n\nvoid FmodEvent::set_2d_attributes(const Transform2D& position) const {\n    FMOD_3D_ATTRIBUTES attr = get_3d_attributes_from_transform2d(position, distanceScale);\n    ERROR_CHECK(_wrapped->set3DAttributes(&attr));\n}\n\nTransform2D FmodEvent::get_2d_attributes() const {\n    Transform2D _2Dattr;\n    FMOD_3D_ATTRIBUTES attr;\n    ERROR_CHECK(_wrapped->get3DAttributes(&attr));\n    _2Dattr = get_transform2d_from_3d_attributes(attr, distanceScale);\n    return _2Dattr;\n}\n\nvoid FmodEvent::set_3d_attributes(const Transform3D& transform) const {\n    FMOD_3D_ATTRIBUTES attr = get_3d_attributes_from_transform3d(transform, distanceScale);\n    ERROR_CHECK(_wrapped->set3DAttributes(&attr));\n}\n\nTransform3D FmodEvent::get_3d_attributes() const {\n    Transform3D _3Dattr;\n    FMOD_3D_ATTRIBUTES attr;\n    ERROR_CHECK(_wrapped->get3DAttributes(&attr));\n    _3Dattr = get_transform3d_from_3d_attributes(attr, distanceScale);\n    return _3Dattr;\n}\n\nvoid FmodEvent::set_node_attributes(Node* node) const {\n    if (node->is_inside_tree()) {\n        if (auto* ci {Node::cast_to<CanvasItem>(node)}) {\n            FMOD_3D_ATTRIBUTES attr = get_3d_attributes_from_transform2d(ci->get_global_transform(), distanceScale);\n            ERROR_CHECK(_wrapped->set3DAttributes(&attr));\n            return;\n        }\n        if (auto* s {Node::cast_to<Node3D>(node)}) {\n            FMOD_3D_ATTRIBUTES attr = get_3d_attributes_from_transform3d(s->get_global_transform(), distanceScale);\n            ERROR_CHECK(_wrapped->set3DAttributes(&attr));\n            return;\n        }\n    }\n    GODOT_LOG_ERROR(\"Invalid Object. A Godot object bound to FMOD has to be either a Node3D or CanvasItem.\")\n}\n\nvoid FmodEvent::set_callback(const Callable& callback, uint32_t p_callback_mask) {\n    eventCallback = callback;\n    callback_mask = p_callback_mask;\n    ERROR_CHECK(_wrapped->setCallback(Callbacks::event_callback, p_callback_mask));\n}\n\nvoid FmodEvent::set_programmer_callback(const String& p_programmers_callback_sound_key) {\n    programmers_callback_sound_key = p_programmers_callback_sound_key;\n    ERROR_CHECK(_wrapped->setCallback(Callbacks::event_callback, callback_mask | FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND | FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND));\n}\n\nconst Callable& FmodEvent::get_callback() const {\n    return eventCallback;\n}\n\nconst String& FmodEvent::get_programmers_callback_sound_key() const {\n    return programmers_callback_sound_key;\n}\n\nvoid FmodEvent::set_distance_scale(float scale){\n    distanceScale = scale;\n}\n\nFmodEvent::~FmodEvent() {\n    if (is_valid()) {\n        _wrapped->setUserData(nullptr);\n    }\n}\n"
  },
  {
    "path": "src/studio/fmod_event.h",
    "content": "#ifndef GODOTFMOD_FMOD_EVENT_H\n#define GODOTFMOD_FMOD_EVENT_H\n\n#include \"classes/ref_counted.hpp\"\n#include \"fmod_studio.hpp\"\n#include \"helpers/common.h\"\n\nnamespace godot {\n    class FmodEvent : public RefCounted {\n        FMODCLASS(FmodEvent, RefCounted, FMOD::Studio::EventInstance);\n\n        Callable eventCallback;\n        String programmers_callback_sound_key;\n        float distanceScale = 1.0f;\n        uint32_t callback_mask;\n\n    public:\n        FmodEvent() = default;\n        ~FmodEvent() override;\n\n        float get_parameter_by_name(const String& parameter_name) const;\n        void set_parameter_by_name(const String& parameter_name, float value) const;\n        void set_parameter_by_name_with_label(const String& parameter_name, const String& label, bool ignoreseekspeed = false) const;\n        float get_parameter_by_id(uint64_t long_id) const;\n        float get_parameter_by_fmod_id(const FMOD_STUDIO_PARAMETER_ID& parameter_id) const;\n        void set_parameter_by_id(uint64_t long_id, float value) const;\n        void set_parameter_by_fmod_id(const FMOD_STUDIO_PARAMETER_ID& parameter_id, float value) const;\n        void set_parameter_by_fmod_id_with_label(const FMOD_STUDIO_PARAMETER_ID& parameter_id, const String& label, bool ignoreseekspeed = false) const;\n        void set_parameter_by_id_with_label(uint64_t parameter_id, const String& label, bool ignoreseekspeed = false) const;\n        void release() const;\n        void start() const;\n        void stop(int stopMode) const;\n        void event_key_off() const;\n        FMOD_STUDIO_PLAYBACK_STATE get_playback_state() const;\n        bool get_paused() const;\n        void set_paused(bool paused) const;\n        float get_pitch() const;\n        void set_pitch(float pitch) const;\n        float get_volume();\n        void set_volume(float volume) const;\n        int get_timeline_position() const;\n        void set_timeline_position(int position) const;\n        float get_reverb_level(int index) const;\n        void set_reverb_level(int index, float level) const;\n        bool is_virtual() const;\n        void set_listener_mask(unsigned int mask) const;\n        uint32_t get_listener_mask() const;\n        Transform3D get_3d_attributes() const;\n        Transform2D get_2d_attributes() const;\n        void set_2d_attributes(const Transform2D& position) const;\n        void set_3d_attributes(const Transform3D& transform) const;\n        void set_node_attributes(Node* node) const;\n        void set_callback(const Callable& callback, uint32_t p_callback_mask);\n        const Callable& get_callback() const;\n        void set_programmer_callback(const String& p_programmers_callback_sound_key);\n        const String& get_programmers_callback_sound_key() const;\n        void set_distance_scale(float scale);\n\n    protected:\n        static void _bind_methods();\n    };\n}// namespace godot\n\nVARIANT_ENUM_CAST(FMOD_STUDIO_PLAYBACK_STATE)\n\n#endif// GODOTFMOD_FMOD_EVENT_H\n"
  },
  {
    "path": "src/studio/fmod_event_description.cpp",
    "content": "#include \"fmod_event_description.h\"\n\n#include \"fmod_event.h\"\n#include \"fmod_parameter_description.h\"\n#include \"helpers/common.h\"\n\nusing namespace godot;\n\nconstexpr const uint32_t PARAMETER_LABEL_BUFFER_SIZE {256};\n\nvoid FmodEventDescription::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"get_length\"), &FmodEventDescription::get_length);\n    ClassDB::bind_method(D_METHOD(\"get_instance_list\"), &FmodEventDescription::get_instance_list);\n    ClassDB::bind_method(D_METHOD(\"get_instance_count\"), &FmodEventDescription::get_instance_count);\n    ClassDB::bind_method(D_METHOD(\"release_all_instances\"), &FmodEventDescription::release_all_instances);\n    ClassDB::bind_method(D_METHOD(\"load_sample_data\"), &FmodEventDescription::load_sample_data);\n    ClassDB::bind_method(D_METHOD(\"unload_sample_data\"), &FmodEventDescription::unload_sample_data);\n    ClassDB::bind_method(D_METHOD(\"get_sample_loading_state\"), &FmodEventDescription::get_sample_loading_state);\n    ClassDB::bind_method(D_METHOD(\"is_3d\"), &FmodEventDescription::is_3d);\n    ClassDB::bind_method(D_METHOD(\"is_one_shot\"), &FmodEventDescription::is_one_shot);\n    ClassDB::bind_method(D_METHOD(\"is_snapshot\"), &FmodEventDescription::is_snapshot);\n    ClassDB::bind_method(D_METHOD(\"is_stream\"), &FmodEventDescription::is_stream);\n    ClassDB::bind_method(D_METHOD(\"has_sustain_point\"), &FmodEventDescription::has_sustain_point);\n    ClassDB::bind_method(D_METHOD(\"get_min_max_distance\"), &FmodEventDescription::get_min_max_distance);\n    ClassDB::bind_method(D_METHOD(\"get_sound_size\"), &FmodEventDescription::get_sound_size);\n    ClassDB::bind_method(D_METHOD(\"get_parameter_by_name\", \"name\"), &FmodEventDescription::get_parameter_by_name);\n    ClassDB::bind_method(\n      D_METHOD(\n        \"get_parameter_by_id\",\n        \"eventPath\"\n        \"idPair\"\n      ),\n      &FmodEventDescription::get_parameter_by_id\n    );\n    ClassDB::bind_method(D_METHOD(\"get_parameter_count\"), &FmodEventDescription::get_parameter_count);\n    ClassDB::bind_method(D_METHOD(\"get_parameter_by_index\", \"index\"), &FmodEventDescription::get_parameter_by_index);\n    ClassDB::bind_method(D_METHOD(\"get_parameters\"), &FmodEventDescription::get_parameters);\n    ClassDB::bind_method(D_METHOD(\"get_parameter_label_by_id\"), &FmodEventDescription::get_parameter_label_by_id);\n    ClassDB::bind_method(D_METHOD(\"get_parameter_label_by_name\"), &FmodEventDescription::get_parameter_label_by_name);\n    ClassDB::bind_method(D_METHOD(\"get_parameter_label_by_index\"), &FmodEventDescription::get_parameter_label_by_index);\n    ClassDB::bind_method(D_METHOD(\"get_parameter_labels_by_id\"), &FmodEventDescription::get_parameter_labels_by_id);\n    ClassDB::bind_method(D_METHOD(\"get_parameter_labels_by_name\"), &FmodEventDescription::get_parameter_labels_by_name);\n    ClassDB::bind_method(D_METHOD(\"get_parameter_labels_by_index\"), &FmodEventDescription::get_parameter_labels_by_index);\n    ClassDB::bind_method(D_METHOD(\"get_user_property\", \"name\"), &FmodEventDescription::get_user_property);\n    ClassDB::bind_method(D_METHOD(\"get_user_property_count\"), &FmodEventDescription::get_user_property_count);\n    ClassDB::bind_method(D_METHOD(\"user_property_by_index\", \"index\"), &FmodEventDescription::user_property_by_index);\n    ClassDB::bind_method(D_METHOD(\"is_valid\"), &FmodEventDescription::is_valid);\n\n    ClassDB::bind_method(D_METHOD(\"get_path\"), &FmodEventDescription::get_path);\n    ClassDB::bind_method(D_METHOD(\"get_guid\"), &FmodEventDescription::get_guid_as_string);\n}\n\nint FmodEventDescription::get_length() {\n    int length = -1;\n    ERROR_CHECK_WITH_REASON(_wrapped->getLength(&length), vformat(\"Cannot get event %s with guid %s length.\", get_path(), get_guid_as_string()));\n    return length;\n}\n\nArray FmodEventDescription::get_instance_list() {\n    Array array;\n\n    int size = 0;\n    if (ERROR_CHECK_WITH_REASON(_wrapped->getInstanceCount(&size), vformat(\"Cannot get instances count for event %s with guid %s\", get_path(), get_guid_as_string()))) {\n        Vector<FMOD::Studio::EventInstance*> instances;\n        instances.resize(size);\n\n        if (ERROR_CHECK_WITH_REASON(_wrapped->getInstanceList(instances.ptrw(), size, &size), vformat(\"Cannot get instances list for event %s with guid %s\", get_path(), get_guid_as_string()))) {\n            for (int i = 0; i < size; ++i) {\n                godot::FmodEvent* event_instance;\n                instances[i]->getUserData((void**) &event_instance);\n                array.append(Ref(event_instance));\n            }\n        }\n    }\n\n    return array;\n}\n\nint FmodEventDescription::get_instance_count() {\n    int count = -1;\n    ERROR_CHECK_WITH_REASON(_wrapped->getInstanceCount(&count), vformat(\"Cannot get instance count for event %s with guid %s\", get_path(), get_guid_as_string()));\n    return count;\n}\n\nvoid FmodEventDescription::release_all_instances() {\n    ERROR_CHECK_WITH_REASON(_wrapped->releaseAllInstances(), vformat(\"Cannot get release all instances for event %s with guid %s\", get_path(), get_guid_as_string()));\n}\n\nvoid FmodEventDescription::load_sample_data() {\n    ERROR_CHECK_WITH_REASON(_wrapped->loadSampleData(), vformat(\"Cannot load sample data for event %s with guid %s\", get_path(), get_guid_as_string()));\n}\n\nvoid FmodEventDescription::unload_sample_data() {\n    ERROR_CHECK_WITH_REASON(_wrapped->unloadSampleData(), vformat(\"Cannot unload sample data for event %s with guid %s\", get_path(), get_guid_as_string()));\n}\n\nint FmodEventDescription::get_sample_loading_state() {\n    FMOD_STUDIO_LOADING_STATE s;\n    ERROR_CHECK_WITH_REASON(_wrapped->getSampleLoadingState(&s), vformat(\"Cannot get sample loading state for event %s with guid %s\", get_path(), get_guid_as_string()));\n    return s;\n}\n\nbool FmodEventDescription::is_3d() {\n    bool is3D = false;\n    ERROR_CHECK_WITH_REASON(_wrapped->is3D(&is3D), vformat(\"Cannot check is_3d for event %s with guid %s\", get_path(), get_guid_as_string()));\n    return is3D;\n}\n\nbool FmodEventDescription::is_one_shot() {\n    bool isOneShot = false;\n    ERROR_CHECK_WITH_REASON(_wrapped->isOneshot(&isOneShot), vformat(\"Cannot check is_one_shot for event %s with guid %s\", get_path(), get_guid_as_string()));\n    return isOneShot;\n}\n\nbool FmodEventDescription::is_snapshot() {\n    bool isSnapshot = false;\n    ERROR_CHECK_WITH_REASON(_wrapped->isSnapshot(&isSnapshot), vformat(\"Cannot check is_snapshot for event %s with guid %s\", get_path(), get_guid_as_string()));\n    return isSnapshot;\n}\n\nbool FmodEventDescription::is_stream() {\n    bool isStream = false;\n    ERROR_CHECK_WITH_REASON(_wrapped->isStream(&isStream), vformat(\"Cannot check is_stream for event %s with guid %s\", get_path(), get_guid_as_string()));\n    return isStream;\n}\n\nbool FmodEventDescription::has_sustain_point() {\n    bool hasSustainPoint = false;\n    ERROR_CHECK_WITH_REASON(_wrapped->hasSustainPoint(&hasSustainPoint), vformat(\"Cannot check has_sustain_point for event %s with guid %s\", get_path(), get_guid_as_string()));\n    return hasSustainPoint;\n}\n\nArray FmodEventDescription::get_min_max_distance() {\n    float minDistance;\n    float maxDistance;\n    Array ret;\n    ERROR_CHECK_WITH_REASON(_wrapped->getMinMaxDistance(&minDistance, &maxDistance), vformat(\"Cannot get min max distance for event %s with guid %s\", get_path(), get_guid_as_string()));\n    ret.append(minDistance);\n    ret.append(maxDistance);\n    return ret;\n}\n\nfloat FmodEventDescription::get_sound_size() {\n    float soundSize = 0.f;\n    ERROR_CHECK_WITH_REASON(_wrapped->getSoundSize(&soundSize), vformat(\"Cannot get sound size for event %s with guid %s\", get_path(), get_guid_as_string()));\n    return soundSize;\n}\n\nRef<FmodParameterDescription> FmodEventDescription::get_parameter_by_name(const String& name) const {\n    Ref<FmodParameterDescription> param_desc;\n    FMOD_STUDIO_PARAMETER_DESCRIPTION fmod_desc;\n    if (ERROR_CHECK_WITH_REASON(_wrapped->getParameterDescriptionByName(name.utf8().get_data(), &fmod_desc),\n                                vformat(\"Cannot get parameter %s description for event %s with guid %s\", name, get_path(), get_guid_as_string()))) {\n        param_desc = FmodParameterDescription::create_ref(fmod_desc);\n    }\n    return param_desc;\n}\n\nRef<FmodParameterDescription> FmodEventDescription::get_parameter_by_id(uint64_t id) const {\n    Ref<FmodParameterDescription> param_desc;\n    FMOD_STUDIO_PARAMETER_ID param_id { ulong_to_fmod_parameter_id(id) };\n    FMOD_STUDIO_PARAMETER_DESCRIPTION fmod_desc;\n    if (ERROR_CHECK_WITH_REASON(_wrapped->getParameterDescriptionByID(param_id, &fmod_desc),\n                                vformat(\"Cannot get parameter %d description for event %s with guid %s\", id, get_path(), get_guid_as_string()))) {\n        param_desc = FmodParameterDescription::create_ref(fmod_desc);\n    }\n    return param_desc;\n}\n\nint FmodEventDescription::get_parameter_count() const {\n    int count = 0;\n    ERROR_CHECK_WITH_REASON(_wrapped->getParameterDescriptionCount(&count),\n                            vformat(\"Cannot get parameter count for event %s with guid %s\", get_path(), get_guid_as_string()));\n    return count;\n}\n\nRef<FmodParameterDescription> FmodEventDescription::get_parameter_by_index(int index) const {\n    Ref<FmodParameterDescription> param_desc;\n    FMOD_STUDIO_PARAMETER_DESCRIPTION fmod_desc;\n    if (ERROR_CHECK_WITH_REASON(_wrapped->getParameterDescriptionByIndex(index, &fmod_desc),\n                                vformat(\"Cannot get parameter with index %d for event %s with guid %s\", index, get_path(), get_guid_as_string()))) {\n        param_desc = FmodParameterDescription::create_ref(fmod_desc);\n    }\n    return param_desc;\n}\n\nArray FmodEventDescription::get_parameters() const {\n    Array parameters;\n    for (int i = 0; i < get_parameter_count(); ++i) {\n        parameters.append(get_parameter_by_index(i));\n    }\n    return parameters;\n}\n\nString FmodEventDescription::get_parameter_label_by_id(uint64_t id, int label_index) const {\n    char label[PARAMETER_LABEL_BUFFER_SIZE];\n    int retrieved;\n    _wrapped->getParameterLabelByID(\n      ulong_to_fmod_parameter_id(id),\n      label_index,\n      label,\n      PARAMETER_LABEL_BUFFER_SIZE,\n      &retrieved\n    );\n    return {label};\n}\n\nString FmodEventDescription::get_parameter_label_by_name(const String& parameter_name, int label_index) const {\n    char label[PARAMETER_LABEL_BUFFER_SIZE];\n    int retrieved;\n    _wrapped->getParameterLabelByName(\n      parameter_name.utf8().get_data(),\n      label_index,\n      label,\n      PARAMETER_LABEL_BUFFER_SIZE,\n      &retrieved\n    );\n    return {label};\n}\n\nString FmodEventDescription::get_parameter_label_by_index(int index, int label_index) const {\n    char label[PARAMETER_LABEL_BUFFER_SIZE];\n    int retrieved;\n    _wrapped->getParameterLabelByIndex(\n      index,\n      label_index,\n      label,\n      PARAMETER_LABEL_BUFFER_SIZE,\n      &retrieved\n    );\n    return {label};\n}\n\nPackedStringArray FmodEventDescription::get_parameter_labels_by_id(uint64_t id) const {\n    PackedStringArray labels;\n\n    Ref<FmodParameterDescription> parameter {get_parameter_by_id(id)};\n    if (!parameter->is_labeled()) {\n        return labels;\n    }\n\n    for (int i = 0; i <= static_cast<int>(parameter->get_maximum()); ++i) {\n        labels.append(get_parameter_label_by_id(id, i));\n    }\n\n    return labels;\n}\n\nPackedStringArray FmodEventDescription::get_parameter_labels_by_name(const String& parameter_name) const {\n    PackedStringArray labels;\n\n    Ref<FmodParameterDescription> parameter {get_parameter_by_name(parameter_name)};\n    if (!parameter->is_labeled()) {\n        return labels;\n    }\n\n    for (int i = 0; i <= static_cast<int>(parameter->get_maximum()); ++i) {\n        labels.append(get_parameter_label_by_name(parameter_name, i));\n    }\n\n    return labels;\n}\n\nPackedStringArray FmodEventDescription::get_parameter_labels_by_index(int index) const {\n    PackedStringArray labels;\n\n    Ref<FmodParameterDescription> parameter {get_parameter_by_index(index)};\n    if (!parameter->is_labeled()) {\n        return labels;\n    }\n\n    for (int i = 0; i <= static_cast<int>(parameter->get_maximum()); ++i) {\n        labels.append(get_parameter_label_by_index(index, i));\n    }\n\n    return labels;\n}\n\nDictionary FmodEventDescription::get_user_property(const String& name) {\n    Dictionary propDesc;\n    FMOD_STUDIO_USER_PROPERTY\n    uProp;\n    if (ERROR_CHECK_WITH_REASON(_wrapped->getUserProperty(name.utf8().get_data(), &uProp),\n                                vformat(\"Cannot get user property %s for event %s with guid %s\", name, get_path(), get_guid_as_string()))) {\n        FMOD_STUDIO_USER_PROPERTY_TYPE fType = uProp.type;\n        if (fType == FMOD_STUDIO_USER_PROPERTY_TYPE_INTEGER) propDesc[String(uProp.name)] = uProp.intvalue;\n        else if (fType == FMOD_STUDIO_USER_PROPERTY_TYPE_BOOLEAN)\n            propDesc[String(uProp.name)] = (bool) uProp.boolvalue;\n        else if (fType == FMOD_STUDIO_USER_PROPERTY_TYPE_FLOAT)\n            propDesc[String(uProp.name)] = uProp.floatvalue;\n        else if (fType == FMOD_STUDIO_USER_PROPERTY_TYPE_STRING)\n            propDesc[String(uProp.name)] = String(uProp.stringvalue);\n    }\n    return propDesc;\n}\n\nint FmodEventDescription::get_user_property_count() {\n    int count = 0;\n    ERROR_CHECK_WITH_REASON(_wrapped->getUserPropertyCount(&count),\n                vformat(\"Cannot get user property count for event %s with guid %s\", get_path(), get_guid_as_string()));\n    return count;\n}\n\nDictionary FmodEventDescription::user_property_by_index(int index) {\n    Dictionary propDesc;\n    FMOD_STUDIO_USER_PROPERTY\n    uProp;\n    if (ERROR_CHECK_WITH_REASON(_wrapped->getUserPropertyByIndex(index, &uProp),\n                                vformat(\"Cannot get user property with index %d for event %s with guid %s\", index, get_path(), get_guid_as_string()))) {\n        FMOD_STUDIO_USER_PROPERTY_TYPE fType = uProp.type;\n        if (fType == FMOD_STUDIO_USER_PROPERTY_TYPE_INTEGER) propDesc[String(uProp.name)] = uProp.intvalue;\n        else if (fType == FMOD_STUDIO_USER_PROPERTY_TYPE_BOOLEAN)\n            propDesc[String(uProp.name)] = (bool) uProp.boolvalue;\n        else if (fType == FMOD_STUDIO_USER_PROPERTY_TYPE_FLOAT)\n            propDesc[String(uProp.name)] = uProp.floatvalue;\n        else if (fType == FMOD_STUDIO_USER_PROPERTY_TYPE_STRING)\n            propDesc[String(uProp.name)] = String(uProp.stringvalue);\n    }\n\n    return propDesc;\n}"
  },
  {
    "path": "src/studio/fmod_event_description.h",
    "content": "#ifndef GODOTFMOD_FMOD_EVENT_DESCRIPTION_H\n#define GODOTFMOD_FMOD_EVENT_DESCRIPTION_H\n\n#include \"classes/ref_counted.hpp\"\n#include \"fmod_parameter_description.h\"\n#include \"fmod_studio.hpp\"\n#include \"helpers/common.h\"\n\nnamespace godot {\n    class FmodEventDescription  : public RefCounted {\n        FMODCLASSWITHPATH(FmodEventDescription, RefCounted, FMOD::Studio::EventDescription);\n\n    public:\n        FmodEventDescription() = default;\n        ~FmodEventDescription() override = default;\n\n        int get_length();\n        Array get_instance_list();\n        int get_instance_count();\n        void release_all_instances();\n        void load_sample_data();\n        void unload_sample_data();\n        int get_sample_loading_state();\n        bool is_3d();\n        bool is_one_shot();\n        bool is_snapshot();\n        bool is_stream();\n        bool has_sustain_point();\n        Array get_min_max_distance();\n        float get_sound_size();\n        Ref<FmodParameterDescription> get_parameter_by_name(const String& name) const;\n        Ref<FmodParameterDescription> get_parameter_by_id(uint64_t id) const;\n        int get_parameter_count() const;\n        Ref<FmodParameterDescription> get_parameter_by_index(int index) const;\n        Array get_parameters() const;\n        String get_parameter_label_by_id(uint64_t id, int label_index) const;\n        String get_parameter_label_by_name(const String& parameter_name, int label_index) const;\n        String get_parameter_label_by_index(int index, int label_index) const;\n        PackedStringArray get_parameter_labels_by_id(uint64_t id) const;\n        PackedStringArray get_parameter_labels_by_name(const String& parameter_name) const;\n        PackedStringArray get_parameter_labels_by_index(int index) const;\n        Dictionary get_user_property(const String& name);\n        int get_user_property_count();\n        Dictionary user_property_by_index(int index);\n\n        protected:\n            static void _bind_methods();\n    };\n}// namespace godot\n#endif// GODOTFMOD_FMOD_EVENT_DESCRIPTION_H\n"
  },
  {
    "path": "src/studio/fmod_parameter_description.cpp",
    "content": "#include \"fmod_parameter_description.h\"\n\n#include <helpers/common.h>\n\nusing namespace godot;\n\nconst String& FmodParameterDescription::get_name() const {\n    return _name;\n}\n\nuint64_t FmodParameterDescription::get_id() const {\n    return fmod_parameter_id_to_ulong(_wrapped.id);\n}\n\nfloat FmodParameterDescription::get_minimum() const {\n    return _wrapped.minimum;\n}\n\nfloat FmodParameterDescription::get_maximum() const {\n    return _wrapped.maximum;\n}\n\nfloat FmodParameterDescription::get_default_value() const {\n    return _wrapped.defaultvalue;\n}\n\nbool FmodParameterDescription::is_read_only() const {\n    return (_wrapped.flags & FMOD_STUDIO_PARAMETER_READONLY) == FMOD_STUDIO_PARAMETER_READONLY;\n}\n\nbool FmodParameterDescription::is_automatic() const {\n    return (_wrapped.flags & FMOD_STUDIO_PARAMETER_AUTOMATIC) == FMOD_STUDIO_PARAMETER_AUTOMATIC;\n}\n\nbool FmodParameterDescription::is_global() const {\n    return (_wrapped.flags & FMOD_STUDIO_PARAMETER_GLOBAL) == FMOD_STUDIO_PARAMETER_GLOBAL;\n}\n\nbool FmodParameterDescription::is_discrete() const {\n    return (_wrapped.flags & FMOD_STUDIO_PARAMETER_DISCRETE) == FMOD_STUDIO_PARAMETER_DISCRETE;\n}\n\nbool FmodParameterDescription::is_labeled() const {\n    return (_wrapped.flags & FMOD_STUDIO_PARAMETER_LABELED) == FMOD_STUDIO_PARAMETER_LABELED;\n}\n\nvoid FmodParameterDescription::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"get_name\"), &FmodParameterDescription::get_name);\n    ClassDB::bind_method(D_METHOD(\"get_id\"), &FmodParameterDescription::get_id);\n    ClassDB::bind_method(D_METHOD(\"get_minimum\"), &FmodParameterDescription::get_minimum);\n    ClassDB::bind_method(D_METHOD(\"get_maximum\"), &FmodParameterDescription::get_maximum);\n    ClassDB::bind_method(D_METHOD(\"get_default_value\"), &FmodParameterDescription::get_default_value);\n    ClassDB::bind_method(D_METHOD(\"is_read_only\"), &FmodParameterDescription::is_read_only);\n    ClassDB::bind_method(D_METHOD(\"is_automatic\"), &FmodParameterDescription::is_automatic);\n    ClassDB::bind_method(D_METHOD(\"is_global\"), &FmodParameterDescription::is_global);\n    ClassDB::bind_method(D_METHOD(\"is_discrete\"), &FmodParameterDescription::is_discrete);\n    ClassDB::bind_method(D_METHOD(\"is_labeled\"), &FmodParameterDescription::is_labeled);\n}"
  },
  {
    "path": "src/studio/fmod_parameter_description.h",
    "content": "#ifndef GODOTFMOD_FMOD_PARAMETER_DESCRIPTION_H\n#define GODOTFMOD_FMOD_PARAMETER_DESCRIPTION_H\n\n#include \"fmod_studio_common.h\"\n#include <classes/ref_counted.hpp>\n\nnamespace godot {\n    class FmodParameterDescription : public RefCounted {\n        GDCLASS(FmodParameterDescription, RefCounted)\n\n        String _name;\n        FMOD_STUDIO_PARAMETER_DESCRIPTION _wrapped;\n\n    public:\n        const String& get_name() const;\n        uint64_t get_id() const;\n        float get_minimum() const;\n        float get_maximum() const;\n        float get_default_value() const;\n        bool is_read_only() const;\n        bool is_automatic() const;\n        bool is_global() const;\n        bool is_discrete() const;\n        bool is_labeled() const;\n\n        inline static Ref<FmodParameterDescription> create_ref(const FMOD_STUDIO_PARAMETER_DESCRIPTION& wrapped) {\n            Ref<FmodParameterDescription> ref;\n            ref.instantiate();\n            ref->_wrapped = wrapped;\n            ref->_name = wrapped.name;\n            return ref;\n        }\n\n    protected:\n        static void _bind_methods();\n    };\n}\n\n#endif// GODOTFMOD_FMOD_PARAMETER_DESCRIPTION_H\n"
  },
  {
    "path": "src/studio/fmod_vca.cpp",
    "content": "#include \"fmod_vca.h\"\n\n#include \"helpers/common.h\"\n\nusing namespace godot;\n\nvoid FmodVCA::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"get_volume\"), &FmodVCA::get_volume);\n    ClassDB::bind_method(D_METHOD(\"set_volume\", \"volume\"), &FmodVCA::set_volume);\n    ClassDB::bind_method(D_METHOD(\"is_valid\"), &FmodVCA::is_valid);\n\n    ClassDB::bind_method(D_METHOD(\"get_path\"), &FmodVCA::get_path);\n    ClassDB::bind_method(D_METHOD(\"get_guid\"), &FmodVCA::get_guid_as_string);\n\n    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, \"volume\",PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_NONE), \"set_volume\", \"get_volume\");\n}\n\nfloat FmodVCA::get_volume() {\n    float volume = 0.0f;\n    ERROR_CHECK_WITH_REASON(_wrapped->getVolume(&volume), vformat(\"Cannot get VCA %s volume.\", get_path()));\n    return volume;\n}\n\nvoid FmodVCA::set_volume(float volume) {\n    ERROR_CHECK_WITH_REASON(_wrapped->setVolume(volume), vformat(\"Cannot set VCA %s volume to %f\", get_path(), volume));\n}"
  },
  {
    "path": "src/studio/fmod_vca.h",
    "content": "#ifndef GODOTFMOD_FMOD_VCA_H\n#define GODOTFMOD_FMOD_VCA_H\n\n#include \"classes/ref_counted.hpp\"\n#include \"fmod_studio.hpp\"\n#include \"helpers/common.h\"\n\nnamespace godot {\n    class FmodVCA  : public RefCounted {\n        FMODCLASSWITHPATH(FmodVCA, RefCounted, FMOD::Studio::VCA);\n\n    public:\n        FmodVCA() = default;\n        ~FmodVCA() override = default;\n\n        float get_volume();\n        void set_volume(float volume);\n\n        protected:\n            static void _bind_methods();\n    };\n}// namespace godot\n#endif// GODOTFMOD_FMOD_VCA_H\n"
  },
  {
    "path": "src/tools/fmod_editor_export_plugin.cpp",
    "content": "#ifdef TOOLS_ENABLED\n\n#include \"fmod_editor_export_plugin.h\"\n#include \"fmod_server.h\"\n#include <plugins/plugins_helper.h>\n\n#include <helpers/common.h>\n#include <helpers/files.h>\n\nusing namespace godot;\n\nconstexpr const char* FMOD_FILE_EXTENSIONS[4] {\".bank\", \".ogg\", \".mp3\", \".wav\"};\nconstexpr const char* ANDROID_BUILD_DIRS[2] = { \"res://android/build\", \"res:///android/build\" };\nconstexpr const char* FMOD_AUTO_EXPORT_BANKS_SETTINGS_KEY = \"fmod/auto_export_banks\";\n\nvoid FmodEditorExportPlugin::_export_begin(const PackedStringArray& features, bool is_debug, const String& path, uint32_t flags) {\n\n    if (get_option(FMOD_AUTO_EXPORT_BANKS_SETTINGS_KEY) != Variant(false)) {\n\n        PackedStringArray excluded_folders;\n        for (const char* dir : ANDROID_BUILD_DIRS) {\n            excluded_folders.append(dir);\n        }\n        for (const char* extension : FMOD_FILE_EXTENSIONS) {\n            PackedStringArray files;\n            list_files_in_folder(files, \"res://\", extension, excluded_folders);\n            for (const String& file : files) {\n                GODOT_LOG_VERBOSE(vformat(\"Adding %s to pck\", file));\n                add_file(file, FileAccess::get_file_as_bytes(file), false);\n            }\n        }\n\n    }\n\n    bool is_windows_export = features.has(\"windows\");\n    bool is_linux_export = features.has(\"linux\");\n    bool is_macos_export = features.has(\"macos\");\n    bool is_ios_export = features.has(\"ios\");\n    bool is_android_export = features.has(\"android\");\n\n    Ref<FmodPluginsSettings> plugins_settings = FmodPluginsSettings::get_from_project_settings();\n\n    if (is_macos_export) {\n        PackedStringArray plugins_libraries_path = _get_libraries_to_export(plugins_settings, \"macos\", \".dylib\");\n        for (const String& library_path : plugins_libraries_path) {\n            add_macos_plugin_file(library_path);\n        }\n    } else if (is_linux_export || is_windows_export) {\n        String target_dir = path.get_base_dir();\n        String os_name = is_linux_export ? \"linux\" : \"windows\";\n        String extension = is_linux_export ? \".so\" : \".dll\";\n        PackedStringArray plugins_libraries_path = _get_libraries_to_export(plugins_settings, os_name, extension);\n\n        Ref<DirAccess> dir_access = DirAccess::open(target_dir);\n\n        if (dir_access.is_null()) {\n            GODOT_LOG_ERROR(vformat(\"Failed to open target directory: %s\", target_dir));\n            return;\n        }\n\n        for (const String& library_path : plugins_libraries_path) {\n            GODOT_LOG_INFO(vformat(\"Will copy %s to %s\", library_path, target_dir));\n            Ref<FileAccess> file_access = FileAccess::open(library_path, FileAccess::READ);\n\n            if (file_access.is_null()) {\n                GODOT_LOG_ERROR(vformat(\"Failed to open library file: %s\", library_path));\n                continue;\n            }\n\n            dir_access->copy(library_path, target_dir.path_join(file_access->get_path().get_file()));\n        }\n    } else if (is_ios_export) {\n        PackedStringArray plugins_libraries_path = _get_libraries_to_export(plugins_settings, \"ios\", \".a\");\n        for (const String& library_path : plugins_libraries_path) {\n            add_ios_project_static_lib(library_path);\n        }\n\n        String cpp_code_declaration = R\"(\n#include<stdint.h>\n#include<cstdlib>\n\nstruct FMOD_DSP_DESCRIPTION;\nstruct FMOD_CODEC_DESCRIPTION;\nstruct FMOD_OUTPUT_DESCRIPTION;\n\ntypedef void* FMOD_SYSTEM_PTR;\n\ntypedef uint32_t (*REGISTER_DSP_METHOD)(FMOD_SYSTEM_PTR system, FMOD_DSP_DESCRIPTION* description, uint32_t* handle);\ntypedef uint32_t (*REGISTER_CODEC_METHOD)(FMOD_SYSTEM_PTR system, FMOD_CODEC_DESCRIPTION* description, uint32_t* handle);\ntypedef uint32_t (*REGISTER_OUTPUT_METHOD)(FMOD_SYSTEM_PTR system, FMOD_OUTPUT_DESCRIPTION* description, uint32_t* handle);\n\ntypedef struct {\n    FMOD_SYSTEM_PTR system;\n    REGISTER_DSP_METHOD register_dsp_method;\n    REGISTER_CODEC_METHOD register_codec_method;\n    REGISTER_OUTPUT_METHOD register_output_method;\n} FMOD_IOS_INTERFACE;\n        )\";\n\n        String cpp_code_external_plugin_declaration = \"extern \\\"C\\\" {\\n\";\n        String cpp_code_load_method = R\"(\nextern \"C\" __attribute__((visibility(\"default\"))) __attribute__((used)) uint32_t* load_all_fmod_plugins(FMOD_IOS_INTERFACE* p_interface, uint32_t* r_count) {\n    FMOD_SYSTEM_PTR fmod_system = p_interface->system;\n    uint32_t handle;\n    uint32_t* handles = reinterpret_cast<uint32_t*>(std::malloc(sizeof(uint32_t) * %s));\n\n)\";\n        const Array& plugin_methods = plugins_settings->get_static_plugins_methods();\n        int64_t method_count = plugin_methods.size();\n        for (int i = 0; i < method_count; ++i) {\n            const Ref<FmodStaticPluginMethod>& plugin_method = plugin_methods[i];\n            const String& method_name = plugin_method->get_method_name();\n            switch (plugin_method->get_type()) {\n                case FmodStaticPluginMethod::CODEC:\n                    cpp_code_external_plugin_declaration +=\n                            vformat(\"    FMOD_CODEC_DESCRIPTION* %s();\\n\", method_name);\n                    cpp_code_load_method +=\n                            vformat(\"    p_interface->register_codec_method(fmod_system, %s(), &handle);\\n\", method_name);\n                    cpp_code_load_method += vformat(\"    handles[%s] = handle;\\n\", i);\n                    break;\n                case FmodStaticPluginMethod::DSP:\n                    cpp_code_external_plugin_declaration +=\n                            vformat(\"    FMOD_DSP_DESCRIPTION* %s();\\n\", method_name);\n                    cpp_code_load_method +=\n                            vformat(\"    p_interface->register_dsp_method(fmod_system, %s(), &handle);\\n\", method_name);\n                    cpp_code_load_method += vformat(\"    handles[%s] = handle;\\n\", i);\n                    break;\n                case FmodStaticPluginMethod::OUTPUT:\n                    cpp_code_external_plugin_declaration +=\n                            vformat(\"    FMOD_OUTPUT_DESCRIPTION* %s();\\n\", method_name);\n                    cpp_code_load_method +=\n                            vformat(\"    p_interface->register_output_method(fmod_system, %s(), &handle);\\n\", method_name);\n                    cpp_code_load_method += vformat(\"    handles[%s] = handle;\\n\", i);\n                    break;\n                case FmodStaticPluginMethod::COUNT:\n                    break;\n            }\n        }\n\n        cpp_code_external_plugin_declaration += \"}\\n\";\n        cpp_code_load_method += R\"(\n    *r_count = %s;\n    return handles;\n}\n)\";\n        add_ios_cpp_code(\n                vformat(\n                        \"%s%s%s\",\n                        cpp_code_declaration,\n                        cpp_code_external_plugin_declaration,\n                        vformat(cpp_code_load_method, method_count, method_count)\n                )\n        );\n    } else if (is_android_export) {\n        bool is_x86 = features.has(\"x86_64\");\n        bool is_arm64 = features.has(\"arm64\");\n\n        if (is_x86) {\n            PackedStringArray plugins_libraries_path = _get_libraries_to_export(plugins_settings, \"android\", \".so\", \"x86_64\");\n\n            PackedStringArray tags;\n            tags.append(\"x86_64\");\n\n            for (const String& library_path : plugins_libraries_path) {\n                add_shared_object(library_path, tags, String());\n            }\n        }\n\n        if (is_arm64) {\n            PackedStringArray plugins_libraries_path = _get_libraries_to_export(plugins_settings, \"android\", \".so\", \"arm64\");\n\n            PackedStringArray tags;\n            tags.append(\"arm64-v8a\");\n\n            for (const String& library_path : plugins_libraries_path) {\n                add_shared_object(library_path, tags, String());\n            }\n        }\n    }\n}\n\nString FmodEditorExportPlugin::_get_name() const {\n    return \"FmodEditorExportPlugin\";\n}\n\nTypedArray<Dictionary> FmodEditorExportPlugin::_get_export_options(const Ref<EditorExportPlatform>& platform) const {\n    TypedArray<Dictionary> options;\n\n    {\n        Dictionary option_dict;\n        Dictionary option;\n        option[\"name\"] = FMOD_AUTO_EXPORT_BANKS_SETTINGS_KEY;\n        option[\"type\"] = Variant::BOOL;\n        \n        option_dict[\"option\"] = option;\n        option_dict[\"default_value\"] = true;\n        option_dict[\"update_visibility\"] = true;\n        \n        options.append(option_dict);\n    }\n    \n    return options;\n}\n\nvoid FmodEditorExportPlugin::_bind_methods() {}\n\nPackedStringArray FmodEditorExportPlugin::_get_libraries_to_export(const Ref<FmodPluginsSettings>& settings, const String& p_os_name, const String& p_extension, const String& p_arch) {\n    PackedStringArray result;\n\n    if (settings.is_null() || settings->get_plugins_base_path().is_empty()) {\n        return result;\n    }\n\n    String plugins_libraries_path = get_plugins_os_directory(settings, p_os_name, p_arch);\n    list_files_in_folder(result, plugins_libraries_path, p_extension);\n\n    return result;\n}\n\n#endif\n"
  },
  {
    "path": "src/tools/fmod_editor_export_plugin.h",
    "content": "#ifdef TOOLS_ENABLED\n\n#ifndef GODOTFMOD_FMOD_EDITOR_EXPORT_PLUGIN_H\n#define GODOTFMOD_FMOD_EDITOR_EXPORT_PLUGIN_H\n\n#include <resources/fmod_plugins_settings.h>\n#include <godot_cpp/classes/editor_export_platform.hpp>\n#include <classes/editor_export_plugin.hpp>\n\nnamespace godot {\n    class FmodEditorExportPlugin : public EditorExportPlugin {\n        GDCLASS(FmodEditorExportPlugin, EditorExportPlugin)\n\n    public:\n        void _export_begin(const PackedStringArray &features, bool is_debug, const String &path, uint32_t flags) override;\n        String _get_name() const override;\n        virtual TypedArray<Dictionary> _get_export_options(const Ref<EditorExportPlatform>& platform) const override;\n\n        static void _bind_methods();\n\n        FmodEditorExportPlugin() = default;\n        ~FmodEditorExportPlugin() = default;\n\n    private:\n        static PackedStringArray _get_libraries_to_export(const Ref<FmodPluginsSettings>& settings, const String& p_os_name, const String& p_extension, const String& p_arch = \"\");\n    };\n}\n\n#endif// GODOTFMOD_FMOD_EDITOR_EXPORT_PLUGIN_H\n\n#endif\n"
  },
  {
    "path": "src/tools/fmod_editor_plugin.cpp",
    "content": "#ifdef TOOLS_ENABLED\n\n#include \"fmod_editor_plugin.h\"\n\n#include \"fmod_editor_export_plugin.h\"\n#include \"classes/os.hpp\"\n#include \"resources/fmod_plugins_settings.h\"\n\n#include <constants.h>\n#include <fmod_server.h>\n#include <helpers/common.h>\n#include <helpers/files.h>\n#include <resources/fmod_dsp_settings.h>\n#include <resources/fmod_settings.h>\n#include <resources/fmod_software_format_settings.h>\n#include <resources/fmod_sound_3d_settings.h>\n#include <resources/fmod_logging_settings.h>\n\n#include <classes/project_settings.hpp>\n\nusing namespace godot;\n\nvoid FmodEditorPlugin::_ready() {\n    add_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodGeneralSettings::INITIALIZE_BASE_PATH, FMOD_SETTING_AUTO_INITIALIZE),\n      DEFAULT_AUTO_INITIALIZE,\n      Variant::Type::BOOL\n    );\n    add_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodGeneralSettings::INITIALIZE_BASE_PATH, FmodGeneralSettings::CHANNEL_COUNT_OPTION),\n      FmodGeneralSettings::DEFAULT_CHANNEL_COUNT,\n      Variant::Type::INT\n    );\n    add_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodGeneralSettings::INITIALIZE_BASE_PATH, FmodGeneralSettings::IS_LIVE_UPDATE_ENABLED_OPTION),\n      FmodGeneralSettings::DEFAULT_IS_LIVEUPDATE,\n      Variant::Type::BOOL\n    );\n    add_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodGeneralSettings::INITIALIZE_BASE_PATH, FmodGeneralSettings::IS_LIVE_MEMORY_TRACKING_ENABLED_OPTION),\n      FmodGeneralSettings::DEFAULT_IS_MEMORY_TRACKING,\n      Variant::Type::BOOL\n    );\n    add_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodSoftwareFormatSettings::SOFTWARE_FORMAT_SETTINGS_BASE_PATH, FmodSoftwareFormatSettings::SAMPLE_RATE_OPTION),\n      FmodSoftwareFormatSettings::DEFAULT_SAMPLE_RATE,\n      Variant::Type::INT\n    );\n    add_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodSoftwareFormatSettings::SOFTWARE_FORMAT_SETTINGS_BASE_PATH, FmodSoftwareFormatSettings::SPEAKER_MODE_OPTION),\n      FmodSoftwareFormatSettings::DEFAULT_SPEAKER_MODE,\n      Variant::Type::INT,\n      PROPERTY_HINT_ENUM,\n      \"DEFAULT,RAW,MONO,STEREO,QUAD,SURROUND,5POINT1,7POINT1,7POINT1POINT4\"\n    );\n    add_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodSoftwareFormatSettings::SOFTWARE_FORMAT_SETTINGS_BASE_PATH, FmodSoftwareFormatSettings::RAW_SPEAKER_COUNT_OPTION),\n      FmodSoftwareFormatSettings::DEFAULT_RAW_SPEAKER_COUNT,\n      Variant::Type::INT\n    );\n    add_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodGeneralSettings::INITIALIZE_BASE_PATH, FmodGeneralSettings::DEFAULT_LISTENER_COUNT_OPTION),\n      FmodGeneralSettings::DEFAULT_DEFAULT_LISTENER_COUNT,\n      Variant::Type::INT\n    );\n\n    String bank_path_option_name =\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodGeneralSettings::INITIALIZE_BASE_PATH, FmodGeneralSettings::BANKS_PATH_OPTION);\n\n    add_setting(bank_path_option_name,\n      FmodGeneralSettings::DEFAULT_BANKS_PATH,\n      Variant::Type::STRING,\n      PROPERTY_HINT_DIR\n    );\n\n    add_setting(\n            vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodPluginsSettings::PLUGINS_SETTINGS_BASE_PATH, FmodPluginsSettings::RESOURCE_OPTION),\n        FmodPluginsSettings::DEFAULT_RESOURCE_OPTION,\n        Variant::Type::STRING,\n        PROPERTY_HINT_FILE\n    );\n\n    add_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodGeneralSettings::INITIALIZE_BASE_PATH, FmodGeneralSettings::SHOULD_LOAD_BY_NAME),\n      FmodGeneralSettings::DEFAULT_SHOULD_LOAD_BY_NAME,\n      Variant::Type::BOOL\n    );\n\n    add_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodDspSettings::DSP_SETTINGS_BASE_PATH, FmodDspSettings::DSP_BUFFER_SIZE_OPTION),\n      FmodDspSettings::DEFAULT_DSP_BUFFER_SIZE,\n      Variant::Type::INT\n    );\n    add_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodDspSettings::DSP_SETTINGS_BASE_PATH, FmodDspSettings::DSP_BUFFER_COUNT_OPTION),\n      FmodDspSettings::DEFAULT_DSP_BUFFER_COUNT,\n      Variant::Type::INT\n    );\n\n    add_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodSound3DSettings::THREE_D_SETTINGS_BASE_PATH, FmodSound3DSettings::DOPPLER_SCALE_OPTION),\n      FmodSound3DSettings::DEFAULT_DOPPLER_SCALE,\n      Variant::Type::FLOAT\n    );\n    add_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodSound3DSettings::THREE_D_SETTINGS_BASE_PATH, FmodSound3DSettings::DISTANCE_FACTOR_OPTION),\n      FmodSound3DSettings::DEFAULT_DISTANCE_FACTOR,\n      Variant::Type::FLOAT\n    );\n    add_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodSound3DSettings::THREE_D_SETTINGS_BASE_PATH, FmodSound3DSettings::ROLLOFF_SCALE_OPTION),\n      FmodSound3DSettings::DEFAULT_ROLLOFF_SCALE,\n      Variant::Type::FLOAT\n    );\n    add_setting(\n    vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodLoggingSettings::LOGGING_SETTINGS_BASE_PATH, FmodLoggingSettings::DEBUG_LEVEL_OPTION),\n    FmodLoggingSettings::DEFAULT_DEBUG_LEVEL,\n    Variant::Type::INT,\n    PROPERTY_HINT_ENUM,\n    \"Inherit,None,Error,Warning,Log,Verbose\"\n  );\n  add_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodLoggingSettings::LOGGING_SETTINGS_BASE_PATH, FmodLoggingSettings::LOG_OUTPUT_OPTION),\n      FmodLoggingSettings::DEFAULT_LOG_OUTPUT,\n      Variant::Type::INT,\n      PROPERTY_HINT_ENUM,\n      \"TTY,File,Godot\"\n    );\n    add_setting(\n      vformat(\"%s/%s/%s\", FMOD_SETTINGS_BASE_PATH, FmodLoggingSettings::LOGGING_SETTINGS_BASE_PATH, FmodLoggingSettings::LOG_FILE_PATH_OPTION),\n      FmodLoggingSettings::DEFAULT_LOG_FILE_PATH,\n      Variant::Type::STRING,\n      PROPERTY_HINT_FILE,\n      \"*.txt,*.log\"\n    );\n}\n\nvoid FmodEditorPlugin::add_setting(\n  const String& p_name,\n  const Variant& p_default_value,\n  Variant::Type p_type,\n  PropertyHint p_hint,\n  const String& p_hint_string\n) {\n    Dictionary setting;\n    setting[\"name\"] = p_name;\n    setting[\"type\"] = p_type;\n    setting[\"hint\"] = p_hint;\n    setting[\"hint_string\"] = p_hint_string;\n\n    if (!ProjectSettings::get_singleton()->has_setting(p_name))\n    {\n        ProjectSettings::get_singleton()->set_setting(p_name, p_default_value);\n    }\n\n    ProjectSettings::get_singleton()->add_property_info(setting);\n    ProjectSettings::get_singleton()->set_as_basic(p_name, true);\n    ProjectSettings::get_singleton()->set_initial_value(p_name, p_default_value);\n}\n\nvoid FmodEditorPlugin::_bind_methods() {}\n\n#endif\n"
  },
  {
    "path": "src/tools/fmod_editor_plugin.h",
    "content": "#ifdef TOOLS_ENABLED\n\n#ifndef GODOTFMOD_FMOD_EDITOR_PLUGIN_H\n#define GODOTFMOD_FMOD_EDITOR_PLUGIN_H\n\n#include \"fmod_editor_export_plugin.h\"\n#include \"studio/fmod_bank.h\"\n\n#include <classes/editor_plugin.hpp>\n\nnamespace godot {\n    class FmodEditorPlugin : public EditorPlugin {\n        GDCLASS(FmodEditorPlugin, EditorPlugin)\n    public:\n        void _ready() override;\n\n        FmodEditorPlugin() = default;\n        ~FmodEditorPlugin() = default;\n\n    private:\n        static void add_setting(\n          const String& p_name,\n          const Variant& p_default_value,\n          Variant::Type p_type,\n          PropertyHint p_hint = PROPERTY_HINT_NONE,\n          const String& p_hint_string = \"\"\n        );\n\n    protected:\n        static void _bind_methods();\n    };\n}\n\n#endif// GODOTFMOD_FMOD_EDITOR_PLUGIN_H\n\n#endif\n"
  }
]